diff --git a/.gitignore b/.gitignore index a6eebd56..2d300346 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ .gradle /local.properties /.idea -.DS_Store +*.iml /build -/captures -.navigation -.directory +/captures/ +.directory \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index d49abf21..de5e87ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,20 @@ language: android -android: - components: - - platform-tools - - tools - - build-tools-26.0.2 - - android-26 - - extra-android-m2repository jdk: - oraclejdk8 + +android: + components: + - platform-tools + - tools + - tools + - build-tools-28.0.2 + - android-28 + - extra-android-m2repository + + licenses: + - android-sdk-license-.+ + - android-sdk-preview-license-.+ + script: - ./gradlew build connectedCheck \ No newline at end of file diff --git a/OpenManga.iml b/OpenManga.iml deleted file mode 100644 index c15cda03..00000000 --- a/OpenManga.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 169ebbc2..47b642c3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ OpenManga - Powerful manga reader for Android with online catalogues. [![Get it on F-Droid](https://cloud.githubusercontent.com/assets/8948226/22860847/7476f5c4-f112-11e6-9031-5ac233d26678.png)](https://f-droid.org/repository/browse/?fdid=org.nv95.openmanga) [![Donate](https://cloud.githubusercontent.com/assets/8948226/26622455/20e44520-45f3-11e7-9257-7c3900697b75.png)](https://money.yandex.ru/to/410012543938752) +## Current status +Development is discontinued now. If you want to help with this project, please, [contact me](https://t.me/Koitharu) + ## Features - 14 online manga's catalogues with search - Downloading manga to local storage diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index a9a90d89..00000000 --- a/app/app.iml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 236e46ae..b285af39 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,24 +1,30 @@ apply plugin: 'com.android.application' -android { - compileSdkVersion 26 - buildToolsVersion "26.0.2" +def gitCommits = 'git rev-list --all --count'.execute([], rootDir).text.trim().toInteger() + +def timestamp = new Date().getTime() +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' defaultConfig { applicationId "org.nv95.openmanga" - minSdkVersion 14 - targetSdkVersion 26 - versionCode 1115 - versionName "preview build" + minSdkVersion 15 + targetSdkVersion 28 + versionCode gitCommits + versionName "5.0" + + resConfigs "en", "ru", "tr", "uk" buildConfigField "boolean", "SELFUPDATE_ENABLED", "true" buildConfigField "String", "SELFUPDATE_URL", "\"http://anibreak.ru/v.0.3/get/openmanga/version\"" - buildConfigField "String", "SYNC_URL", "\"http://46.36.36.38:5000/api/v1\"" + buildConfigField "String", "SYNC_URL", "\"http://openmanga.pythonanywhere.com/api/v1\"" + buildConfigField "long", "TIMESTAMP", "${timestamp}L" } signingConfigs { debug { storeFile file("debug.jks") - storePassword 'develop' + storePassword "develop" keyAlias "develop" keyPassword "develop" } @@ -26,40 +32,54 @@ android { buildTypes { release { - minifyEnabled false - shrinkResources false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + setProperty("archivesBaseName", "OpenManga-v${defaultConfig.versionName}") + resValue "string", "app_name", "OpenManga" } - debug { + debug { + zipAlignEnabled true applicationIdSuffix ".debug" - minifyEnabled false - shrinkResources false - signingConfig signingConfigs.debug + versionNameSuffix="a" + signingConfig signingConfigs.debug + resValue "string", "app_name", "OpenManga Debug" + } + + fdroid { + initWith release + buildConfigField "boolean", "SELFUPDATE_ENABLED", "false" + versionNameSuffix="-fdroid" } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + lintOptions { disable 'MissingTranslation' abortOnError false } - allprojects { - repositories { - jcenter() - google() - maven { url "https://jitpack.io" } - } - } +} + +ext { + supportLib = '28.0.0' } dependencies { + implementation "com.android.support:design:${supportLib}" + implementation "com.android.support:support-v4:${supportLib}" + implementation "com.android.support:recyclerview-v7:${supportLib}" + implementation "com.android.support:cardview-v7:${supportLib}" + implementation "com.android.support:exifinterface:${supportLib}" - compile 'com.android.support:design:26.1.0' - compile 'com.android.support:recyclerview-v7:26.1.0' - compile 'com.android.support:cardview-v7:26.1.0' - compile 'org.jsoup:jsoup:1.10.3' - compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0' - compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' - compile 'com.soundcloud.android:android-crop:1.0.1@aar' - compile 'com.getkeepsafe.taptargetview:taptargetview:1.9.1' - compile 'info.guardianproject.netcipher:netcipher:2.0.0-alpha1' + implementation 'com.squareup.okhttp3:okhttp:3.11.0' + implementation 'com.squareup.duktape:duktape-android:1.3.0' + implementation 'info.guardianproject.netcipher:netcipher:2.0.0-beta1' + implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.0.0-alpha1' + implementation 'org.jsoup:jsoup:1.11.3' + implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' + implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7b52c41f..2df912c1 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,11 +1,11 @@ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified -# in /media/HDD1/Other/Android/Sdk/tools/proguard/proguard-android.txt +# in C:/$USER/JTM/AppData/Local/Android/Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html +# https://developer.android.com/studio/build/shrink-code.html # Add any project specific keep options here: @@ -15,3 +15,17 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} + +-keeppackagenames org.jsoup.nodes + +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform \ No newline at end of file diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml deleted file mode 100644 index b9f9a9b5..00000000 --- a/app/src/debug/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - OpenManga Debug - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f1cc58c4..a0e09bb0 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,95 +1,263 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:tools="http://schemas.android.com/tools" + package="org.nv95.openmanga" + android:installLocation="auto"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/org/nv95/openmanga/AppBaseActivity.java b/app/src/main/java/org/nv95/openmanga/AppBaseActivity.java new file mode 100644 index 00000000..831e9d74 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/AppBaseActivity.java @@ -0,0 +1,193 @@ +package org.nv95.openmanga; + +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.ColorRes; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Toast; + +import org.nv95.openmanga.common.utils.ThemeUtils; + +import java.util.ArrayList; + +/** + * Created by koitharu on 21.12.17. + */ + +public abstract class AppBaseActivity extends AppCompatActivity { + + private boolean mActionBarVisible = false; + private boolean mHomeAsUpEnabled = false; + private int mTheme = 0; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mTheme = ThemeUtils.getAppTheme(this); + setTheme(ThemeUtils.getAppThemeRes(mTheme)); + } + + public boolean isDarkTheme() { + return mTheme > 7; + } + + public void enableHomeAsUp() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null && !mHomeAsUpEnabled) { + actionBar.setDisplayHomeAsUpEnabled(true); + mHomeAsUpEnabled = true; + } + } + + public void enableHomeAsClose() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null && !mHomeAsUpEnabled) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_light); + mHomeAsUpEnabled = true; + } + } + + public void disableTitle() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(false); + } + } + + @Override + public void setSupportActionBar(@Nullable Toolbar toolbar) { + super.setSupportActionBar(toolbar); + mActionBarVisible = toolbar != null; + } + + public void setSupportActionBar(@IdRes int toolbarId) { + setSupportActionBar(findViewById(toolbarId)); + } + + public void hideActionBar() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null && mActionBarVisible) { + mActionBarVisible = false; + actionBar.hide(); + } + } + + public void showActionBar() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null && !mActionBarVisible) { + mActionBarVisible = true; + actionBar.show(); + } + } + + public void toggleActionBar() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + if (!mActionBarVisible) { + mActionBarVisible = true; + actionBar.show(); + } else { + mActionBarVisible = false; + actionBar.hide(); + } + } + } + + public boolean isActionBarVisible() { + return mActionBarVisible; + } + + public int getActivityTheme() { + return mTheme; + } + + public void setSubtitle(@Nullable CharSequence subtitle) { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setSubtitle(subtitle); + } + } + + public void setSubtitle(@StringRes int subtitle) { + setSubtitle(getString(subtitle)); + } + + public void enableTransparentStatusBar(@ColorRes int color) { + if (Build.VERSION.SDK_INT >= 21) { + Window window = getWindow(); + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + if (color != 0) { + window.setStatusBarColor(ContextCompat.getColor(this, color)); + } + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home && mHomeAsUpEnabled) { + finish(); + } + return super.onOptionsItemSelected(item); + } + + public void setKeepScreenOn(boolean flag) { + if (flag) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + + public void checkPermissions(int requestCode, String... permissions) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + for (String o : permissions) { + onPermissionGranted(requestCode, o); + } + } + final ArrayList required = new ArrayList<>(permissions.length); + for (String o : permissions) { + if (ContextCompat.checkSelfPermission(this, o) != PackageManager.PERMISSION_GRANTED) { + required.add(o); + } else { + onPermissionGranted(requestCode, o); + } + } + if (!required.isEmpty()) { + ActivityCompat.requestPermissions(this, required.toArray(new String[required.size()]), requestCode); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + for (int i = 0; i < permissions.length; i++) { + if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + onPermissionGranted(requestCode, permissions[i]); + } + } + } + + protected void onPermissionGranted(int requestCode, String permission) { + + } + + protected void stub() { + Toast.makeText(this, "Not implemented", Toast.LENGTH_SHORT).show(); + } +} + diff --git a/app/src/main/java/org/nv95/openmanga/AppBaseDialogFragment.java b/app/src/main/java/org/nv95/openmanga/AppBaseDialogFragment.java new file mode 100644 index 00000000..8ea43660 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/AppBaseDialogFragment.java @@ -0,0 +1,21 @@ +package org.nv95.openmanga; + +import android.support.v7.app.AppCompatDialogFragment; +import android.view.ViewGroup; +import android.view.Window; + +/** + * Created by koitharu on 23.01.18. + */ + +public abstract class AppBaseDialogFragment extends AppCompatDialogFragment { + + @Override + public void onResume() { + super.onResume(); + Window window = getDialog().getWindow(); + if (window != null) { + window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/AppBaseFragment.java b/app/src/main/java/org/nv95/openmanga/AppBaseFragment.java new file mode 100644 index 00000000..86743d8f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/AppBaseFragment.java @@ -0,0 +1,22 @@ +package org.nv95.openmanga; + +import android.app.Fragment; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by koitharu on 21.12.17. + */ + +public abstract class AppBaseFragment extends Fragment { + + protected View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @LayoutRes int resource) { + return inflater.inflate(resource, container, false); + } + + public void scrollToTop() { + } +} diff --git a/app/src/main/java/org/nv95/openmanga/AsyncService.java b/app/src/main/java/org/nv95/openmanga/AsyncService.java new file mode 100644 index 00000000..0d754865 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/AsyncService.java @@ -0,0 +1,153 @@ +package org.nv95.openmanga; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Created by koitharu on 25.01.18. + */ + +public abstract class AsyncService extends Service implements Handler.Callback { + + private static final int MSG_PRE_EXECUTE = 1; + private static final int MSG_POST_EXECUTE = 2; + private static final int MSG_UPDATE_PROGRESS = 3; + private static final int MSG_STOP_SELF = 4; + + private Handler mHandler; + @Nullable + private BackgroundThread mThread; + private LinkedBlockingQueue mQueue; + private final AtomicBoolean mCancelled = new AtomicBoolean(); + + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(this); + mQueue = new LinkedBlockingQueue<>(2); + mThread = null; + } + + @Override + public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + final String action = intent == null ? null : intent.getAction(); + //noinspection ConstantConditions + if (action != null && onNewIntent(action, intent.getExtras())) { + //TODO + } else { + stopSelf(); + } + return START_NOT_STICKY; + } + + public abstract boolean onNewIntent(@NonNull String action, @NonNull Bundle extras); + + public boolean onStopService() { + return true; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + @SuppressWarnings("unchecked") + public final boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_STOP_SELF: + if (onStopService()) { + stopSelf(); + } + return true; + case MSG_PRE_EXECUTE: + onPreExecute((R) msg.obj); + return true; + case MSG_POST_EXECUTE: + onPostExecute((R) msg.obj, msg.arg1); + return true; + case MSG_UPDATE_PROGRESS: + onProgressUpdate(msg.arg1, msg.arg2, msg.obj); + return true; + default: + return false; + } + } + + @MainThread + public abstract void onPreExecute(R r); + + @WorkerThread + public abstract int doInBackground(R r); + + @MainThread + public abstract void onPostExecute(R r, int result); + + @WorkerThread + protected final void setProgress(int progress, int max, @Nullable Object extra) { + Message msg = Message.obtain(); + msg.what = MSG_UPDATE_PROGRESS; + msg.arg1 = progress; + msg.arg2 = max; + msg.obj = extra; + mHandler.sendMessage(msg); + } + + public abstract void onProgressUpdate(int progress, int max, @Nullable Object extra); + + @MainThread + public void startBackground(R r) { + try { + mQueue.put(r); + if (mThread == null) { + mThread = new BackgroundThread(); + } + mCancelled.set(false); + mThread.start(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @MainThread + public void cancelBackground() { + mCancelled.set(true); + } + + public boolean isCancelled() { + return mCancelled.get(); + } + + private final class BackgroundThread extends Thread { + + @Override + public void run() { + R r; + while (!mCancelled.get() && (r = mQueue.poll()) != null) { + Message msg = Message.obtain(); + msg.what = MSG_PRE_EXECUTE; + msg.obj = r; + mHandler.sendMessage(msg); + final int result = doInBackground(r); + msg = Message.obtain(); + msg.what = MSG_POST_EXECUTE; + msg.arg1 = result; + msg.obj = r; + mHandler.sendMessage(msg); + } + mHandler.sendEmptyMessage(MSG_STOP_SELF); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/MainActivity.java b/app/src/main/java/org/nv95/openmanga/MainActivity.java new file mode 100644 index 00000000..aad23a8e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/MainActivity.java @@ -0,0 +1,188 @@ +package org.nv95.openmanga; + +import android.app.Fragment; +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.ComponentName; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomNavigationView; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.PopupMenu; +import android.support.v7.widget.SearchView; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; + +import org.nv95.openmanga.common.CrashHandler; +import org.nv95.openmanga.core.storage.FlagsStorage; +import org.nv95.openmanga.discover.DiscoverFragment; +import org.nv95.openmanga.search.SearchActivity; +import org.nv95.openmanga.shelf.OnTipsActionListener; +import org.nv95.openmanga.shelf.ShelfFragment; +import org.nv95.openmanga.tools.ToolsFragment; + +/** + * Created by koitharu on 21.12.17. + */ + +public final class MainActivity extends AppBaseActivity implements BottomNavigationView.OnNavigationItemSelectedListener, + BottomNavigationView.OnNavigationItemReselectedListener, View.OnClickListener, PopupMenu.OnMenuItemClickListener, + OnTipsActionListener { + + private BottomNavigationView mBottomNavigationView; + private PopupMenu mMainMenu; + private View mContent; + private AppBaseFragment mFragment; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + setSupportActionBar(R.id.toolbar); + + mBottomNavigationView = findViewById(R.id.bottomNavView); + ImageView mImageViewMenu = findViewById(R.id.imageViewMenu); + SearchView mSearchView = findViewById(R.id.searchView); + mContent = findViewById(R.id.content); + + mMainMenu = new PopupMenu(this, mImageViewMenu); + mMainMenu.setOnMenuItemClickListener(this); + + mBottomNavigationView.setOnNavigationItemSelectedListener(this); + mBottomNavigationView.setOnNavigationItemReselectedListener(this); + mImageViewMenu.setOnClickListener(this); + + final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); + final SearchableInfo searchableInfo; + if (searchManager != null) { + searchableInfo = searchManager.getSearchableInfo(new ComponentName(this, SearchActivity.class)); + mSearchView.setSearchableInfo(searchableInfo); + } + + mFragment = new ShelfFragment(); + initMenu(); + getFragmentManager().beginTransaction() + .replace(R.id.content, mFragment) + .commitAllowingStateLoss(); + } + + @Override + protected void onResume() { + super.onResume(); + mContent.requestFocus(); + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + mFragment.onDestroyOptionsMenu(); + switch (item.getItemId()) { + case R.id.section_shelf: + mFragment = new ShelfFragment(); + break; + case R.id.section_discover: + mFragment = new DiscoverFragment(); + break; + case R.id.action_tools: + mFragment = new ToolsFragment(); + break; + default: + return false; + } + initMenu(); + getFragmentManager().beginTransaction() + .replace(R.id.content, mFragment) + .commit(); + return true; + } + + + @Override + public void onNavigationItemReselected(@NonNull MenuItem item) { + Fragment fragment = getFragmentManager().findFragmentById(R.id.content); + if (fragment != null && fragment instanceof AppBaseFragment) { + ((AppBaseFragment) fragment).scrollToTop(); + } + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.imageViewMenu: + showMainMenu(); + break; + } + } + + private void initMenu() { + mMainMenu.getMenu().clear(); + mFragment.onCreateOptionsMenu(mMainMenu.getMenu(), mMainMenu.getMenuInflater()); + mMainMenu.inflate(R.menu.options_main); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + + default: + return mFragment.onOptionsItemSelected(item); + } + } + + private void onPrepareMenu(Menu menu) { + } + + private void showMainMenu() { + onPrepareMenu(mMainMenu.getMenu()); + mFragment.onPrepareOptionsMenu(mMainMenu.getMenu()); + mMainMenu.show(); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU) { + showMainMenu(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public void onTipActionClick(int actionId) { + switch (actionId) { + case R.id.action_crash_report: + final CrashHandler crashHandler = CrashHandler.get(); + if (crashHandler != null) { + new AlertDialog.Builder(this) + .setTitle(crashHandler.getErrorClassName()) + .setMessage(crashHandler.getErrorMessage() + "\n\n" + crashHandler.getErrorStackTrace()) + .setNegativeButton(R.string.close, null) + .create() + .show(); + } + break; + case R.id.action_discover: + mBottomNavigationView.setSelectedItemId(R.id.section_discover); + break; + } + } + + @Override + public void onTipDismissed(int actionId) { + switch (actionId) { + case R.id.action_crash_report: + final CrashHandler crashHandler = CrashHandler.get(); + if (crashHandler != null) { + crashHandler.clear(); + } + break; + case R.id.action_wizard: + FlagsStorage.get(this).setWizardRequired(false); + break; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/MangaListLoader.java b/app/src/main/java/org/nv95/openmanga/MangaListLoader.java deleted file mode 100644 index 6fa4aae2..00000000 --- a/app/src/main/java/org/nv95/openmanga/MangaListLoader.java +++ /dev/null @@ -1,236 +0,0 @@ -package org.nv95.openmanga; - -import android.os.AsyncTask; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; - -import org.nv95.openmanga.adapters.EndlessAdapter; -import org.nv95.openmanga.adapters.MangaListAdapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.lists.MangaList; - -/** - * Created by nv95 on 25.01.16. - */ -public class MangaListLoader implements EndlessAdapter.OnLoadMoreListener { - - private RecyclerView mRecyclerView; - @NonNull - private final MangaListAdapter mAdapter; - private final OnContentLoadListener mContentLoadListener; - @NonNull - private final MangaList mList; - @Nullable - private LoadContentTask mTaskInstance; - - public MangaListLoader(RecyclerView recyclerView, @NonNull OnContentLoadListener listener) { - mRecyclerView = recyclerView; - mContentLoadListener = listener; - mList = new MangaList(); - mAdapter = new MangaListAdapter(mList, mRecyclerView); - mRecyclerView.setAdapter(mAdapter); - mAdapter.setOnLoadMoreListener(this); - } - - public void attach(RecyclerView recyclerView) { - mRecyclerView = recyclerView; - mAdapter.attach(recyclerView); - mRecyclerView.setAdapter(mAdapter); - } - - @Override - public void onLoadMore() { - new LoadContentTask(mList.getPagesCount(), true).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public MangaListAdapter getAdapter() { - return mAdapter; - } - - public void clearItems() { - clearItemsLazy(); - if (mContentLoadListener != null) { - mContentLoadListener.onContentLoaded(true); - } - } - - public int getCurrentPage() { - return mList.getPagesCount(); - } - - public void loadFromPage(int page) { - mList.clear(); - mAdapter.notifyDataSetChanged(); - mList.setPagesCount(page); - mList.setHasNext(true); - cancelLoading(); - new LoadContentTask(page, true).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void loadContent(boolean appendable, boolean invalidate) { - if (invalidate) { - mList.clear(); - mAdapter.notifyDataSetChanged(); - } - mList.setHasNext(appendable); - cancelLoading(); - new LoadContentTask(0, appendable).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void addItem(MangaInfo data) { - if (mList.add(data)) { - mAdapter.notifyItemInserted(mList.size() - 1); - if (mList.size() == 1 && mContentLoadListener != null) { - mContentLoadListener.onContentLoaded(true); - } - } - } - - public void addItem(MangaInfo data, int position) { - mList.add(position, data); - mAdapter.notifyItemInserted(position); - if (mList.size() == 1 && mContentLoadListener != null) { - mContentLoadListener.onContentLoaded(true); - } - } - - public void removeItem(int position) { - mList.remove(position); - mAdapter.notifyItemRemoved(position); - if (mList.size() == 0 && mContentLoadListener != null) { - mContentLoadListener.onContentLoaded(true); - } - } - - public void notifyRemoved(int position) { - mAdapter.notifyItemRemoved(position); - if (mList.size() == 0 && mContentLoadListener != null) { - mContentLoadListener.onContentLoaded(true); - } - } - - public void moveItem(int from, int to) { - MangaInfo item = mList.get(from); - mList.remove(from); - mList.add(to - (from >= to ? 0 : 1), item); - mAdapter.notifyItemMoved(from, to); - } - - public void updateItem(int pos, MangaInfo data) { - mList.set(pos, data); - mAdapter.notifyItemChanged(pos); - } - - public void cancelLoading() { - if (mTaskInstance != null && mTaskInstance.getStatus() != AsyncTask.Status.FINISHED) { - mTaskInstance.cancel(true); - } - } - - public MangaList getList() { - return mList; - } - - public int getContentSize() { - return mList.size(); - } - - public void clearItemsLazy() { - mList.clear(); - mAdapter.notifyDataSetChanged(); - } - - public interface OnContentLoadListener { - void onContentLoaded(boolean success); - - void onLoadingStarts(boolean hasItems); - - @Nullable - MangaList onContentNeeded(int page); - } - - public void updateLayout(boolean grid, int spanCount, ThumbSize thumbSize) { - GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); - int position = layoutManager.findFirstCompletelyVisibleItemPosition(); - layoutManager.setSpanCount(spanCount); - layoutManager.setSpanSizeLookup(new AutoSpanSizeLookup(spanCount)); - mAdapter.setThumbnailsSize(thumbSize); - if (mAdapter.setGrid(grid)) { - mRecyclerView.setAdapter(mAdapter); - } - mRecyclerView.scrollToPosition(position); - } - - public MangaInfo[] getItems(int[] positions) { - MangaInfo[] items = new MangaInfo[positions.length]; - for (int i=0;i { - private final int mPage; - private final boolean mAppendable; - - public LoadContentTask(int page, boolean appendable) { - this.mPage = page; - cancelLoading(); - mTaskInstance = this; - mAppendable = appendable; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mContentLoadListener.onLoadingStarts(!mList.isEmpty()); - } - - @Override - protected MangaList doInBackground(Void... params) { - return mContentLoadListener.onContentNeeded(mPage); - } - - @Override - protected void onPostExecute(MangaList list) { - super.onPostExecute(list); - if (list == null) { - mList.setHasNext(false); - mAdapter.notifyItemChanged(mList.size()); - mContentLoadListener.onContentLoaded(false); - return; - } - mList.appendPage(list); - if (list.size() != 0) { - if (mList.size() == list.size()) { - mAdapter.notifyDataSetChanged(); - } else { - mAdapter.notifyItemRangeInserted(mList.size() - 1, mList.size() - list.size()); - } - mList.setHasNext(mAppendable); - } else { - mList.setHasNext(false); - mAdapter.notifyItemChanged(mList.size()); - } - mAdapter.setLoaded(); - mContentLoadListener.onContentLoaded(true); - mTaskInstance = null; - } - } - - public class AutoSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { - final int mCount; - - public AutoSpanSizeLookup(int mCount) { - this.mCount = mCount; - } - - @Override - public int getSpanSize(int position) { - return mAdapter.getItemViewType(position) == 0 ? mCount : 1; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/OpenMangaApp.java b/app/src/main/java/org/nv95/openmanga/OpenMangaApp.java new file mode 100644 index 00000000..96b91beb --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/OpenMangaApp.java @@ -0,0 +1,62 @@ +package org.nv95.openmanga; + +import android.app.Activity; +import android.app.Application; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; + +import org.nv95.openmanga.common.CrashHandler; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.utils.network.CookieStore; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.storage.settings.AppSettings; +import org.nv95.openmanga.updchecker.JobSetupReceiver; + +/** + * Created by koitharu on 24.12.17. + */ + +public final class OpenMangaApp extends Application { + + private CrashHandler mCrashHandler; + + @Override + public void onCreate() { + super.onCreate(); + mCrashHandler = new CrashHandler(this); + Thread.setDefaultUncaughtExceptionHandler(mCrashHandler); + final AppSettings settings = AppSettings.get(this); + ImageUtils.init(this); + CookieStore.getInstance().init(this); + NetworkUtils.init(this, settings.isUseTor()); + ResourceUtils.setLocale(getResources(), settings.getAppLocale()); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + if (!prefs.getBoolean(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, false)) { + PreferenceManager.setDefaultValues(this, R.xml.pref_appearance, true); + PreferenceManager.setDefaultValues(this, R.xml.pref_network, true); + //TODO other + JobSetupReceiver.setup(this); + prefs.edit().putBoolean(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES, true).apply(); + } + } + + public void restart() { + final Intent intent = getBaseContext().getPackageManager() + .getLaunchIntentForPackage(getBaseContext().getPackageName()); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + @NonNull + public CrashHandler getCrashHandler() { + return mCrashHandler; + } + + @NonNull + public static OpenMangaApp from(Activity activity) { + return (OpenMangaApp) activity.getApplication(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/OpenMangaApplication.java b/app/src/main/java/org/nv95/openmanga/OpenMangaApplication.java deleted file mode 100755 index 3c968b2b..00000000 --- a/app/src/main/java/org/nv95/openmanga/OpenMangaApplication.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.nv95.openmanga; - -import android.app.Application; -import android.content.res.Resources; -import android.preference.PreferenceManager; -import android.text.TextUtils; -import android.util.DisplayMetrics; - -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.utils.AnimUtils; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.NetworkUtils; - -import java.util.Locale; - -/** - * Created by nv95 on 10.12.15. - */ -public class OpenMangaApplication extends Application { - - @Override - public void onCreate() { - super.onCreate(); - FileLogger.init(this); - Resources resources = getResources(); - final float aspectRatio = 18f / 13f; - ThumbSize.THUMB_SIZE_LIST = new ThumbSize( - resources.getDimensionPixelSize(R.dimen.thumb_width_list), - resources.getDimensionPixelSize(R.dimen.thumb_height_list) - ); - ThumbSize.THUMB_SIZE_SMALL = new ThumbSize( - resources.getDimensionPixelSize(R.dimen.thumb_width_small), - aspectRatio - ); - ThumbSize.THUMB_SIZE_MEDIUM = new ThumbSize( - resources.getDimensionPixelSize(R.dimen.thumb_width_medium), - aspectRatio - ); - ThumbSize.THUMB_SIZE_LARGE = new ThumbSize( - resources.getDimensionPixelSize(R.dimen.thumb_width_large), - aspectRatio - ); - - ImageUtils.init(this); - AnimUtils.init(this); - NetworkUtils.setUseTor(this, PreferenceManager.getDefaultSharedPreferences(this).getBoolean("use_tor", false)); - ScheduledServiceReceiver.enable(this); - setLanguage(getResources(), PreferenceManager.getDefaultSharedPreferences(this).getString("lang", "")); - } - - public static void setLanguage(Resources res, String lang) { - DisplayMetrics dm = res.getDisplayMetrics(); - android.content.res.Configuration conf = res.getConfiguration(); - conf.locale = TextUtils.isEmpty(lang) ? Locale.getDefault() : new Locale(lang); - res.updateConfiguration(conf, dm); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/ScheduledServiceReceiver.java b/app/src/main/java/org/nv95/openmanga/ScheduledServiceReceiver.java deleted file mode 100755 index 4cfae435..00000000 --- a/app/src/main/java/org/nv95/openmanga/ScheduledServiceReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.nv95.openmanga; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; - -import org.nv95.openmanga.services.ScheduledService; -import org.nv95.openmanga.utils.FileLogger; - -/** - * Created by nv95 on 19.12.15. - */ -public class ScheduledServiceReceiver extends BroadcastReceiver { - - public static final long SCHEDULE_INTERVAL = AlarmManager.INTERVAL_HOUR / 30; - - public static void enable(Context context) { - Intent intent = new Intent(context, ScheduledService.class); - PendingIntent pIntent = PendingIntent.getService(context, 478, intent, 0); - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, - SCHEDULE_INTERVAL, SCHEDULE_INTERVAL, pIntent); - } - - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case Intent.ACTION_BOOT_COMPLETED: - case "android.intent.action.QUICKBOOT_POWERON": - enable(context); - break; - case ConnectivityManager.CONNECTIVITY_ACTION: - context.startService(new Intent(context, ScheduledService.class)); - break; - default: - FileLogger.getInstance().report("--ScheduledServiceReceiver unknown action"); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/SharedFileProvider.java b/app/src/main/java/org/nv95/openmanga/SharedFileProvider.java new file mode 100644 index 00000000..83f5ea83 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/SharedFileProvider.java @@ -0,0 +1,11 @@ +package org.nv95.openmanga; + +import android.support.v4.content.FileProvider; + +/** + * Created by koitharu on 30.01.18. + */ + +public final class SharedFileProvider extends FileProvider { + public static final String AUTHORITY = "org.nv95.openmanga.files"; +} diff --git a/app/src/main/java/org/nv95/openmanga/activities/AboutActivity.java b/app/src/main/java/org/nv95/openmanga/activities/AboutActivity.java deleted file mode 100644 index aed32058..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/AboutActivity.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.os.Bundle; -import android.support.v7.widget.Toolbar; -import android.text.Html; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.InternalLinkMovement; - -/** - * Created by nv95 on 12.01.16. - */ -public class AboutActivity extends BaseAppActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_about); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - enableHomeAsUp(); - TextView textView = (TextView) findViewById(R.id.textView); - assert textView != null; - textView.setText(Html.fromHtml(AppHelper.getRawString(this, R.raw.about))); - textView.setMovementMethod(new InternalLinkMovement(null)); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/BaseAppActivity.java b/app/src/main/java/org/nv95/openmanga/activities/BaseAppActivity.java deleted file mode 100644 index 662d980e..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/BaseAppActivity.java +++ /dev/null @@ -1,302 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.annotation.ColorRes; -import android.support.annotation.IdRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.MenuItem; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.Toast; - -import com.getkeepsafe.taptargetview.TapTarget; -import com.getkeepsafe.taptargetview.TapTargetView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.NetworkUtils; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; - -/** - * Created by nv95 on 19.02.16. - */ -public abstract class BaseAppActivity extends AppCompatActivity { - - private static final int REQUEST_PERMISSION = 112; - - private boolean mActionBarVisible = false; - private boolean mHomeAsUpEnabled = false; - private int mTheme = 0; - @Nullable - private ArrayList> mLoaders; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mTheme = LayoutUtils.getAppTheme(this); - setTheme(LayoutUtils.getAppThemeRes(mTheme)); - } - - public boolean isDarkTheme() { - return mTheme > 7; - } - - public void enableHomeAsUp() { - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null && !mHomeAsUpEnabled) { - actionBar.setDisplayHomeAsUpEnabled(true); - mHomeAsUpEnabled = true; - } - } - - public void enableHomeAsClose() { - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null && !mHomeAsUpEnabled) { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_light); - mHomeAsUpEnabled = true; - } - } - - public void disableTitle() { - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayShowTitleEnabled(false); - } - } - - @Override - public void setSupportActionBar(@Nullable Toolbar toolbar) { - super.setSupportActionBar(toolbar); - mActionBarVisible = toolbar != null; - } - - void setupToolbarScrolling(Toolbar toolbar) { - setToolbarScrollingLock(toolbar, false); - } - - void setToolbarScrollingLock(Toolbar toolbar, boolean lock) { - if (toolbar == null || !(toolbar.getParent() instanceof AppBarLayout)) { - return; - } - boolean scrolls = !lock && PreferenceManager.getDefaultSharedPreferences(this).getBoolean("hide_toolbars", true); - AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); - params.setScrollFlags(scrolls ? AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS : 0); - } - - public void setSupportActionBar(@IdRes int toolbarId) { - setSupportActionBar((Toolbar) findViewById(toolbarId)); - } - - public void hideActionBar() { - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null && mActionBarVisible) { - mActionBarVisible = false; - actionBar.hide(); - } - } - - public void showActionBar() { - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null && !mActionBarVisible) { - mActionBarVisible = true; - actionBar.show(); - } - } - - public void toggleActionBar() { - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - if (!mActionBarVisible) { - mActionBarVisible = true; - actionBar.show(); - } else { - mActionBarVisible = false; - actionBar.hide(); - } - } - } - - public boolean isActionBarVisible() { - return mActionBarVisible; - } - - public int getActivityTheme() { - return mTheme; - } - - public void setSubtitle(@Nullable CharSequence subtitle) { - final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setSubtitle(subtitle); - } - } - - public void setSubtitle(@StringRes int subtitle) { - setSubtitle(getString(subtitle)); - } - - public void enableTransparentStatusBar(@ColorRes int color) { - if (Build.VERSION.SDK_INT >= 21) { - Window window = getWindow(); - window.getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - if (color != 0) { - window.setStatusBarColor(ContextCompat.getColor(this, color)); - } - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home && mHomeAsUpEnabled) { - finish(); - } - return super.onOptionsItemSelected(item); - } - - public void keepScreenOn() { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - public boolean checkPermission(String permission) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return true; - } - if (ContextCompat.checkSelfPermission(this, - permission) == PackageManager.PERMISSION_GRANTED) { - return true; - } - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) { - ActivityCompat.requestPermissions(this, - new String[]{permission}, - REQUEST_PERMISSION); - } - return false; - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { - if (requestCode == REQUEST_PERMISSION) { - for (int i = 0; i < permissions.length; i++) { - if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - onPermissionGranted(permissions[i]); - } - } - } - } - - protected void onPermissionGranted(String permission) { - - } - - public void showToast(CharSequence text, int gravity, int delay) { - final Toast toast = Toast.makeText(this, text, delay); - toast.setGravity(gravity, 0, 0); - toast.show(); - } - - public void showToast(@StringRes int text, int gravity, int delay) { - showToast(getString(text), gravity, delay); - } - - public boolean checkConnectionWithSnackbar(View view) { - if (NetworkUtils.checkConnection(this)) { - return true; - } else { - Snackbar.make(view, R.string.no_network_connection, Snackbar.LENGTH_SHORT).show(); - return false; - } - } - - public void registerLoaderTask(AsyncTask task) { - if (mLoaders == null) { - mLoaders = new ArrayList<>(); - } - mLoaders.add(new WeakReference<>(task)); - } - - @Override - protected void onDestroy() { - if (mLoaders != null) { - for (WeakReference o : mLoaders) { - AsyncTask task = o.get(); - if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { - task.cancel(true); - } - } - } - mLoaders = null; - super.onDestroy(); - } - - protected boolean showcase(final View view, @StringRes int title, @StringRes int body) { - return showcase(view, title, body, false); - } - - protected boolean showcase(final View view, @StringRes int title, @StringRes int body, boolean tint) { - boolean dark = isDarkTheme(); - if (view != null && view.getVisibility() == View.VISIBLE - && !getSharedPreferences("tips", MODE_PRIVATE).getBoolean(getClass().getSimpleName() + "_" + view.getId(), false)) { - TapTargetView.showFor(this, - TapTarget.forView(view, getString(title), getString(body)) - .transparentTarget(!tint) - .textColorInt(Color.WHITE) - .dimColorInt(LayoutUtils.getAttrColor(this, R.attr.colorPrimaryDark)) - .tintTarget(tint), - new TapTargetView.Listener() { // The listener can listen for regular clicks, long clicks or cancels - @Override - public void onTargetClick(TapTargetView view1) { - super.onTargetClick(view1); - } - }); - SharedPreferences prefs = getSharedPreferences("tips", MODE_PRIVATE); - prefs.edit().putBoolean(BaseAppActivity.this.getClass().getSimpleName() + "_" + view.getId(), true).apply(); - return true; - } else { - return false; - } - } - - /** - * @param menuItemId - * @param title - * @param body - * @return true if showcase shown - */ - protected boolean showcase(@IdRes final int menuItemId, @StringRes int title, @StringRes int body) { - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - return toolbar != null && showcase(toolbar.findViewById(menuItemId), title, body, false); - } - - /** - * @return true only once for activity - */ - protected boolean isFirstStart() { - SharedPreferences prefs = getSharedPreferences("tips", MODE_PRIVATE); - if (prefs.getBoolean(getClass().getName(), true)) { - prefs.edit().putBoolean(getClass().getName(), false).apply(); - return true; - } - return false; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/DownloadsActivity.java b/app/src/main/java/org/nv95/openmanga/activities/DownloadsActivity.java deleted file mode 100644 index f6e45bd8..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/DownloadsActivity.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.DownloadsAdapter; -import org.nv95.openmanga.helpers.MangaSaveHelper; - -/** - * Created by nv95 on 03.01.16. - */ -public class DownloadsActivity extends BaseAppActivity { - - private DownloadsAdapter mAdapter; - private TextView mTextViewHolder; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_downloads); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - enableHomeAsClose(); - RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); - assert mRecyclerView != null; - mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - mTextViewHolder = (TextView) findViewById(R.id.textView_holder); - mAdapter = new DownloadsAdapter(mRecyclerView); - mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - super.onChanged(); - mTextViewHolder.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); - } - }); - mRecyclerView.setAdapter(mAdapter); - } - - @Override - protected void onStart() { - super.onStart(); - mAdapter.enable(); - } - - @Override - protected void onStop() { - mAdapter.disable(); - super.onStop(); - } - - @Override - protected void onResume() { - super.onResume(); - mAdapter.notifyDataSetChanged(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.downloads, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_cancel: - new AlertDialog.Builder(this) - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new MangaSaveHelper(DownloadsActivity.this).cancelAll(); - } - }) - .setMessage(R.string.downloads_cancel_confirm) - .create().show(); - return true; - case R.id.action_resume: - mAdapter.setTaskPaused(false); - invalidateOptionsMenu(); - return true; - case R.id.action_pause: - mAdapter.setTaskPaused(true); - invalidateOptionsMenu(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/FileSelectActivity.java b/app/src/main/java/org/nv95/openmanga/activities/FileSelectActivity.java deleted file mode 100644 index 844e0488..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/FileSelectActivity.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.Manifest; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.FileSelectAdapter; -import org.nv95.openmanga.dialogs.DirSelectDialog; -import org.nv95.openmanga.dialogs.StorageSelectDialog; - -import java.io.File; - -/** - * Created by nv95 on 09.02.16. - */ -public class FileSelectActivity extends BaseAppActivity implements DirSelectDialog.OnDirSelectListener, View.OnClickListener { - - public static final String EXTRA_INITIAL_DIR = "initial_dir"; - public static final String EXTRA_FILTER = "filter"; - - @Nullable - private FileSelectAdapter mAdapter; - private RecyclerView mRecyclerView; - private File mDir; - private String mFilter; - private TextView mTextViewTitle; - - private final RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - super.onChanged(); - File file = mAdapter.getCurrentDir(); - mTextViewTitle.setText(file.getPath()); - } - }; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_importfile); - setSupportActionBar(R.id.toolbar); - enableHomeAsUp(); - mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); - mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - mTextViewTitle = (TextView) findViewById(R.id.textView_title); - findViewById(R.id.imageButton).setOnClickListener(this); - String dir = getIntent().getStringExtra(EXTRA_INITIAL_DIR); - if (dir == null) { - dir = getSharedPreferences(this.getLocalClassName(), MODE_PRIVATE) - .getString("dir", null); - } - mDir = dir == null ? Environment.getExternalStorageDirectory() - : new File(dir); - mFilter = getIntent().getStringExtra(EXTRA_FILTER); - if (checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) { - onPermissionGranted(null); - } - } - - @Override - protected void onPermissionGranted(String permission) { - mAdapter = new FileSelectAdapter(mDir, mFilter, this); - mAdapter.registerAdapterDataObserver(mObserver); - mObserver.onChanged(); - mRecyclerView.setAdapter(mAdapter); - } - - @SuppressWarnings("ConstantConditions") - @Override - public void onDirSelected(File dir) { - if (dir.isDirectory()) { - mAdapter.setDirectory(dir); - } else { - getSharedPreferences(this.getLocalClassName(), MODE_PRIVATE).edit() - .putString("dir", mAdapter.getCurrentDir().getPath()) - .apply(); - Intent data = new Intent(); - data.putExtra(Intent.EXTRA_TEXT, dir.getPath()); - setResult(RESULT_OK, data); - finish(); - } - } - - @Override - public void onClick(View view) { - if (mAdapter == null) { - return; - } - new StorageSelectDialog(this, true) - .setDirSelectListener(new DirSelectDialog.OnDirSelectListener() { - @Override - public void onDirSelected(File dir) { - mAdapter.setCurrentDir(dir); - } - }) - .show(); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/MainActivity.java b/app/src/main/java/org/nv95/openmanga/activities/MainActivity.java deleted file mode 100755 index 2b40625f..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/MainActivity.java +++ /dev/null @@ -1,826 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.Manifest; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.NavigationView; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; -import android.support.v4.util.Pair; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.text.Html; -import android.util.Log; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; -import android.view.SubMenu; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.nv95.openmanga.MangaListLoader; -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.settings.SettingsActivity2; -import org.nv95.openmanga.adapters.EndlessAdapter; -import org.nv95.openmanga.adapters.GenresSortAdapter; -import org.nv95.openmanga.components.OnboardSnackbar; -import org.nv95.openmanga.dialogs.BookmarksDialog; -import org.nv95.openmanga.dialogs.FastHistoryDialog; -import org.nv95.openmanga.dialogs.NavigationListener; -import org.nv95.openmanga.dialogs.PageNumberDialog; -import org.nv95.openmanga.dialogs.RecommendationsPrefDialog; -import org.nv95.openmanga.helpers.ContentShareHelper; -import org.nv95.openmanga.helpers.ListModeHelper; -import org.nv95.openmanga.helpers.MangaSaveHelper; -import org.nv95.openmanga.helpers.SyncHelper; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.RecommendationsProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.providers.staff.ProviderSummary; -import org.nv95.openmanga.services.ExportService; -import org.nv95.openmanga.services.ImportService; -import org.nv95.openmanga.services.SyncService; -import org.nv95.openmanga.utils.AnimUtils; -import org.nv95.openmanga.utils.ChangesObserver; -import org.nv95.openmanga.utils.DeltaUpdater; -import org.nv95.openmanga.utils.DrawerHeaderImageTool; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.InternalLinkMovement; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.ProgressAsyncTask; -import org.nv95.openmanga.utils.StorageUpgradeTask; -import org.nv95.openmanga.utils.choicecontrol.ModalChoiceCallback; -import org.nv95.openmanga.utils.choicecontrol.ModalChoiceController; - -import java.io.File; -import java.util.List; - -public class MainActivity extends BaseAppActivity implements - View.OnClickListener, MangaListLoader.OnContentLoadListener, ChangesObserver.OnMangaChangesListener, - ListModeHelper.OnListModeListener, GenresSortAdapter.Callback, NavigationView.OnNavigationItemSelectedListener, - InternalLinkMovement.OnLinkClickListener, ModalChoiceCallback, View.OnLongClickListener, NavigationListener { - - private static final int REQUEST_IMPORT = 792; - private static final int REQUEST_SETTINGS = 795; - //views - private RecyclerView mRecyclerView; - private DrawerLayout mDrawerLayout; - private ActionBarDrawerToggle mToggle; - private FloatingActionButton mFab; - private TextView mTextViewHolder; - private ProgressBar mProgressBar; - //utils - private MangaListLoader mListLoader; - private MangaProviderManager mProviderManager; - private ListModeHelper mListModeHelper; - //data - private MangaProvider mProvider; - private GenresSortAdapter mGenresAdapter; - private NavigationView mNavigationView; - private int mSelectedItem; - private DrawerHeaderImageTool mDrawerHeaderTool; - - private final BroadcastReceiver mSyncReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getIntExtra("what", -1)) { - case SyncService.MSG_FAV_FINISHED: - if (mSelectedItem == R.id.nav_action_favourites) { - new DeltaUpdater(mListLoader).update(FavouritesProvider.getInstance(MainActivity.this)); - } - break; - case SyncService.MSG_HIST_FINISHED: - if (mSelectedItem == R.id.nav_action_history) { - new DeltaUpdater(mListLoader).update(HistoryProvider.getInstance(MainActivity.this)); - } - break; - } - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - enableTransparentStatusBar(android.R.color.transparent); - Toolbar toolbar; - setSupportActionBar(toolbar = (Toolbar) findViewById(R.id.toolbar)); - setupToolbarScrolling(toolbar); - WelcomeActivity.show(this); - - mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - mFab = (FloatingActionButton) findViewById(R.id.fab_read); - mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); - mTextViewHolder = (TextView) findViewById(R.id.textView_holder); - mProgressBar = (ProgressBar) findViewById(R.id.progressBar); - - RecyclerView genresRecyclerView = (RecyclerView) findViewById(R.id.recyclerViewGenres); - genresRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - genresRecyclerView.setAdapter(mGenresAdapter = new GenresSortAdapter(this)); - - mTextViewHolder.setMovementMethod(new InternalLinkMovement(this)); - mFab.setOnClickListener(this); - mFab.setOnLongClickListener(this); - mFab.setVisibility(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) - .getBoolean("fab", true) ? View.VISIBLE : View.GONE); - mNavigationView = (NavigationView) findViewById(R.id.navigation_drawer); - mNavigationView.setNavigationItemSelectedListener(this); - mRecyclerView.setLayoutManager(new GridLayoutManager(this, 1)); - mProviderManager = new MangaProviderManager(this); - int defSection = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) - .getString("defsection", String.valueOf(MangaProviderManager.PROVIDER_LOCAL))); - final MenuItem menuItem = mNavigationView.getMenu().getItem(4 + defSection); - menuItem.setChecked(true); - mSelectedItem = menuItem.getItemId(); - mToggle = new ActionBarDrawerToggle(this, mDrawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close) { - @Override - public void onDrawerOpened(View drawerView) { - super.onDrawerOpened(drawerView); - invalidateOptionsMenu(); - } - - @Override - public void onDrawerClosed(View drawerView) { - super.onDrawerClosed(drawerView); - invalidateOptionsMenu(); - } - }; - mDrawerLayout.addDrawerListener(mToggle); - - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setHomeButtonEnabled(true); - } - - initDrawerRemoteProviders(); - - setTitle(getResources().getStringArray(R.array.section_names)[4 + defSection]); - mProvider = mProviderManager.getProviderById(defSection); - mListLoader = new MangaListLoader(mRecyclerView, this); - mListLoader.getAdapter().getChoiceController().setCallback(this); - mListLoader.getAdapter().getChoiceController().setEnabled(true); - mListModeHelper = new ListModeHelper(this, this); - mListModeHelper.applyCurrent(); - mListModeHelper.enable(); - StorageUpgradeTask.doUpgrade(this); - mGenresAdapter.fromProvider(this, mProvider); - mListLoader.loadContent(mProvider.isMultiPage(), true); - - if (isDarkTheme()) { - ColorStateList csl = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.white_overlay_85)); - mNavigationView.setItemTextColor(csl); - mNavigationView.setItemIconTintList(csl); - } - //Load saved image in drawer head - mDrawerHeaderTool = new DrawerHeaderImageTool(this, mNavigationView); - mDrawerHeaderTool.initDrawerImage(); - if (isFirstStart()) { - mDrawerLayout.postDelayed(new Runnable() { - @Override - public void run() { - mDrawerLayout.openDrawer(GravityCompat.START); - } - }, 700); - } - - ChangesObserver.getInstance().addListener(this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE); - } - registerReceiver(mSyncReceiver, new IntentFilter(SyncService.SYNC_EVENT)); - } - - /** - * Добавляем remote providers в левое меню - */ - private void initDrawerRemoteProviders() { - - SubMenu navMenu = mNavigationView.getMenu().findItem(R.id.nav_remote_storage).getSubMenu(); - navMenu.removeGroup(R.id.groupRemote); - List providers = mProviderManager.getOrderedProviders(); - for (int i = 0; i < mProviderManager.getProvidersCount(); i++) { - navMenu.add(R.id.groupRemote, providers.get(i).id, i, providers.get(i).name); - } - navMenu.setGroupCheckable(R.id.groupRemote, true, true); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.main, menu); - return super.onCreateOptionsMenu(menu); - } - - private int getCurrentProviderIndex(){ - switch (mSelectedItem){ - case R.id.nav_local_storage: return MangaProviderManager.PROVIDER_LOCAL; - case R.id.nav_action_favourites: return MangaProviderManager.PROVIDER_FAVOURITES; - case R.id.nav_action_history: return MangaProviderManager.PROVIDER_HISTORY; - case R.id.nav_action_recommendations: return MangaProviderManager.PROVIDER_RECOMMENDATIONS; - default: return mSelectedItem; - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.setGroupVisible(R.id.group_local, mSelectedItem == R.id.nav_local_storage); - menu.setGroupVisible(R.id.group_history, mSelectedItem == R.id.nav_action_history); - menu.setGroupVisible(R.id.group_favourites, mSelectedItem == R.id.nav_action_favourites); - menu.findItem(R.id.action_goto).setVisible(mProvider.isMultiPage()); - menu.findItem(R.id.action_recommend_opts).setVisible(mSelectedItem == R.id.nav_action_recommendations); - SyncHelper syncHelper = SyncHelper.get(this); - menu.findItem(R.id.action_sync).setVisible( - syncHelper.isAuthorized() && ( - (mSelectedItem == R.id.nav_action_history && syncHelper.isHistorySyncEnabled()) || - (mSelectedItem == R.id.nav_action_favourites && syncHelper.isFavouritesSyncEnabled()) - ) - ); - mListModeHelper.onPrepareOptionsMenu(menu); - return super.onPrepareOptionsMenu(menu); - } - - @Override - protected void onDestroy() { - unregisterReceiver(mSyncReceiver); - mListLoader.cancelLoading(); - ChangesObserver.getInstance().removeListener(this); - mListModeHelper.disable(); - super.onDestroy(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_IMPORT && resultCode == RESULT_OK) { - String f = data.getStringExtra(Intent.EXTRA_TEXT); - if (f == null) { - return; - } - f = new File(f).getName(); - new AlertDialog.Builder(this) - .setMessage(getString(R.string.import_file_confirm, f)) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.import_file, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startService(new Intent(MainActivity.this, ImportService.class) - .putExtras(data).putExtra("action", ImportService.ACTION_START)); - } - }) - .create().show(); - } else if (requestCode == REQUEST_SETTINGS || requestCode == WelcomeActivity.REQUEST_ONBOARDING) { - if (getActivityTheme() != LayoutUtils.getAppTheme(this)) { - recreate(); - return; - } - setupToolbarScrolling((Toolbar) findViewById(R.id.toolbar)); - mFab.setVisibility(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) - .getBoolean("fab", true) ? View.VISIBLE : View.GONE); - if (mProviderManager != null && mNavigationView != null){ - mProviderManager.update(); - initDrawerRemoteProviders(); - mNavigationView.setCheckedItem(mSelectedItem); - } - } else { - mDrawerHeaderTool.onActivityResult(requestCode, resultCode, data); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (mToggle.onOptionsItemSelected(item)) { - return true; - } - switch (item.getItemId()) { - case R.id.action_search: - startActivity(new Intent(MainActivity.this, SearchActivity.class) - .putExtra("provider", getCurrentProviderIndex())); - return true; - case R.id.action_import: - startActivityForResult(new Intent(this, FileSelectActivity.class) - .putExtra(FileSelectActivity.EXTRA_FILTER, "cbz;zip"), REQUEST_IMPORT); - return true; - case R.id.action_goto: - new PageNumberDialog(this) - .setNavigationListener(this) - .show(mListLoader.getCurrentPage()); - return true; - case R.id.action_recommend_opts: - new RecommendationsPrefDialog(this, this).show(); - return true; - case R.id.action_sync: - SyncService.start(this); - Snackbar.make(mRecyclerView, R.string.sync_started, Snackbar.LENGTH_SHORT).show(); - return true; - case R.id.action_filter: - mDrawerLayout.openDrawer(GravityCompat.END); - return true; - case R.id.action_bookmarks: - new BookmarksDialog(this).show(); - return true; - case R.id.action_histclear: - if (mProvider instanceof HistoryProvider) { - new AlertDialog.Builder(MainActivity.this) - .setCancelable(true) - .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (((HistoryProvider) mProvider).clear()) { - mListLoader.clearItems(); - } else { - Snackbar.make(mRecyclerView, R.string.error, Snackbar.LENGTH_SHORT).show(); - } - } - }) - .setNegativeButton(android.R.string.cancel, null) - .setMessage(R.string.history_will_cleared) - .create().show(); - } - return true; - case R.id.action_updates: - startActivity(new Intent(this, NewChaptersActivity.class)); - return true; - case R.id.action_settings: - startActivityForResult(new Intent(this, SettingsActivity2.class), REQUEST_SETTINGS); - return true; - default: - return mListModeHelper.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); - } - } - - @Override - public void onApply(int genre, int sort, @Nullable String genreName, @Nullable String sortName) { - mDrawerLayout.closeDrawer(GravityCompat.END); - setSubtitle(genre == 0 ? null : genreName); - MangaProviderManager.saveSortOrder(MainActivity.this, mProvider, sort); - updateContent(); - } - - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.nav_action_settings: - startActivityForResult(new Intent(this, SettingsActivity2.class), REQUEST_SETTINGS); - return true; - case R.id.nav_local_storage: - mProvider = LocalMangaProvider.getInstance(this); - break; - case R.id.nav_action_recommendations: - mProvider = RecommendationsProvider.getInstance(this); - break; - case R.id.nav_action_favourites: - mProvider = FavouritesProvider.getInstance(this); - break; - case R.id.nav_action_history: - mProvider = HistoryProvider.getInstance(this); - break; - default: - mProvider = mProviderManager.getProviderById(item.getItemId()); - break; - } - mSelectedItem = item.getItemId(); - mGenresAdapter.fromProvider(this, mProvider); - setSubtitle(null); - mDrawerLayout.closeDrawer(GravityCompat.START); - setTitle(mProvider.getName()); - updateContent(); - return true; - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - mToggle.syncState(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mToggle.onConfigurationChanged(newConfig); - mListModeHelper.applyCurrent(); - } - - @Override - public void onBackPressed() { - if (mDrawerLayout.isDrawerOpen(GravityCompat.END)) { - mDrawerLayout.closeDrawer(GravityCompat.END); - } else if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) { - mDrawerLayout.closeDrawer(GravityCompat.START); - } else { - super.onBackPressed(); - } - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.fab_read: - new OpenLastTask(this).start(true); - break; - } - } - - @Override - public boolean onLongClick(View v) { - switch (v.getId()) { - case R.id.fab_read: - //new OpenLastTask(this).attach(this).start(false); - new FastHistoryDialog(this).show(3); - return true; - default: - return false; - } - } - - @Override - public void onContentLoaded(boolean success) { - AnimUtils.crossfade(mProgressBar, null); - if (mListLoader.getContentSize() == 0) { - String holder; - if (mProvider instanceof LocalMangaProvider) { - holder = getString(R.string.no_saved_manga); - } else if (mProvider instanceof FavouritesProvider) { - holder = getString(R.string.no_favourites); - } else if (mProvider instanceof HistoryProvider) { - holder = getString(R.string.history_empty); - } else { - holder = getString(R.string.no_manga_found); - } - mTextViewHolder.setText(holder); - AnimUtils.crossfade(null, mTextViewHolder); - if (!success) { - if (!NetworkUtils.checkConnection(this) && MangaProviderManager.needConnectionFor(mProvider)) { - mTextViewHolder.setText(Html.fromHtml(getString(R.string.no_network_connection_html))); - } else { - Snackbar.make(mRecyclerView, R.string.loading_error, Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.retry, new View.OnClickListener() { - @Override - public void onClick(View v) { - updateContent(); - } - }) - .show(); - } - } - } else { - mRecyclerView.postDelayed(new ListTipHelper(), 500); - } - } - - @Override - public void onLoadingStarts(boolean hasItems) { - if (!hasItems) { - AnimUtils.noanim(mTextViewHolder, mProgressBar); - mListLoader.getAdapter().getChoiceController().clearSelection(); - } - } - - @Nullable - @Override - public MangaList onContentNeeded(int page) { - try { - return mProvider.getList(page, mGenresAdapter.getSelectedSort(), mGenresAdapter.getSelectedGenre()); - } catch (Exception e) { - Log.e("OCN", e.getMessage()); - return null; - } - } - - @Override - public void onListModeChanged(boolean grid, int sizeMode) { - int spans; - ThumbSize thumbSize; - switch (sizeMode) { - case -1: - spans = LayoutUtils.isTabletLandscape(this) ? 2 : 1; - thumbSize = ThumbSize.THUMB_SIZE_LIST; - break; - case 0: - spans = LayoutUtils.getOptimalColumnsCount(getResources(), thumbSize = ThumbSize.THUMB_SIZE_SMALL); - break; - case 1: - spans = LayoutUtils.getOptimalColumnsCount(getResources(), thumbSize = ThumbSize.THUMB_SIZE_MEDIUM); - break; - case 2: - spans = LayoutUtils.getOptimalColumnsCount(getResources(), thumbSize = ThumbSize.THUMB_SIZE_LARGE); - break; - default: - return; - } - mListLoader.updateLayout(grid, spans, thumbSize); - } - - @Override - public void onLinkClicked(TextView view, String scheme, String url) { - switch (url) { - case "update": - updateContent(); - break; - } - } - - private void updateContent() { - mListLoader.loadContent(mProvider.isMultiPage(), true); - } - - @Override - public boolean onCreateActionMode(android.view.ActionMode mode, Menu menu) { - getMenuInflater().inflate(R.menu.actionmode_mangas, menu); - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - return true; - } - - @Override - public boolean onPrepareActionMode(android.view.ActionMode mode, Menu menu) { - menu.findItem(R.id.action_remove).setVisible(mProvider.isItemsRemovable()); - menu.findItem(R.id.action_save).setVisible(mSelectedItem != R.id.nav_local_storage); - menu.findItem(R.id.action_share).setVisible(mSelectedItem != R.id.nav_local_storage); - menu.findItem(R.id.action_move).setVisible(mSelectedItem == R.id.nav_action_favourites); - menu.findItem(R.id.action_export).setVisible(mSelectedItem == R.id.nav_local_storage); - menu.findItem(R.id.action_select_all).setVisible( - mSelectedItem == R.id.nav_action_favourites - || mSelectedItem == R.id.nav_action_history - || mSelectedItem == R.id.nav_local_storage - ); - return false; - } - - @Override - public boolean onActionItemClicked(android.view.ActionMode mode, MenuItem item) { - final int[] items = mListLoader.getAdapter().getChoiceController().getSelectedItemsPositions(); - final long[] ids = new long[items.length]; - for (int i=0;i> implements DialogInterface.OnCancelListener { - - OpenLastTask(MainActivity mainActivity) { - super(mainActivity); - setCancelable(true); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected Pair doInBackground(Boolean... params) { - try { - Intent intent; - HistoryProvider historyProvider = HistoryProvider.getInstance(getActivity()); - MangaInfo info = historyProvider.getLast(); - if (info == null) { - return new Pair<>(2, null); - } - if (params.length != 0 && !params[0]) { - intent = new Intent(getActivity(), PreviewActivity2.class); - intent.putExtras(info.toBundle()); - return new Pair<>(0, intent); - } - MangaProvider provider; - if (info.provider.equals(LocalMangaProvider.class)) { - provider = LocalMangaProvider.getInstance(getActivity()); - } else { - if (!NetworkUtils.checkConnection(getActivity())) { - provider = LocalMangaProvider.getInstance(getActivity()); - info = ((LocalMangaProvider)provider).getLocalManga(info); - if (info.provider != LocalMangaProvider.class) { - return new Pair<>(1, null); - } - } else { - provider = MangaProviderManager.instanceProvider(getActivity(), info.provider); - } - } - MangaSummary summary = provider.getDetailedInfo(info); - intent = new Intent(getActivity(), ReadActivity2.class); - intent.putExtras(summary.toBundle()); - HistoryProvider.HistorySummary hs = historyProvider.get(info); - if (hs != null) { - int index = summary.chapters.indexByNumber(hs.getChapter()); - if (index != -1) { - intent.putExtra("chapter", index); - intent.putExtra("page", hs.getPage()); - } - } - return new Pair<>(0, intent); - } catch (Exception e) { - FileLogger.getInstance().report("OPENLAST", e); - return new Pair<>(3, null); - } - } - - @Override - protected void onPostExecute(@NonNull BaseAppActivity mainActivity, Pair result) { - int msg; - switch (result.first) { - case 0: - mainActivity.startActivity(result.second); - return; - case 1: - msg = R.string.no_network_connection; - break; - case 2: - msg = R.string.history_empty; - break; - default: - msg = R.string.error; - break; - } - new AlertDialog.Builder(mainActivity) - .setCancelable(true) - .setPositiveButton(R.string.close, null) - .setMessage(mainActivity.getString(msg)) - .create().show(); - } - } - - private class ListTipHelper implements Runnable { - - @SuppressWarnings("StatementWithEmptyBody") - @Override - public void run() { - if (mProvider instanceof FavouritesProvider && OnboardSnackbar.askOnce(mRecyclerView, R.string.tip_chapter_checking, R.string.no_thanks, R.string.configure, new View.OnClickListener() { - @Override - public void onClick(View view) { - SettingsActivity2.openChaptersCheckSettings(MainActivity.this, 0); - } - })) { - //done - } else if (mProvider instanceof HistoryProvider || mProvider instanceof FavouritesProvider) { - OnboardSnackbar.askOnce(mRecyclerView, R.string.sync_tip, R.string.no_thanks, R.string.configure, new View.OnClickListener() { - @Override - public void onClick(View view) { - SettingsActivity2.openSyncSettings(MainActivity.this, 0); - } - }); - } else if (mProvider instanceof RecommendationsProvider) { - OnboardSnackbar.askOnce(mRecyclerView, R.string.recommendations_tip, R.string.skip, R.string.configure, new View.OnClickListener() { - @Override - public void onClick(View view) { - new RecommendationsPrefDialog(MainActivity.this, MainActivity.this).show(); - } - }); - } else if (MangaProviderManager.needConnectionFor(mProvider)) { //returns true on online provider - showcase(R.id.action_search, R.string.action_search, R.string.tip_search_main); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/NewChaptersActivity.java b/app/src/main/java/org/nv95/openmanga/activities/NewChaptersActivity.java deleted file mode 100644 index e498d91d..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/NewChaptersActivity.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.NewChaptersAdapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.NewChaptersProvider; -import org.nv95.openmanga.utils.AnimUtils; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.io.IOException; -import java.util.Map; - -/** - * Created by nv95 on 17.04.16. - */ -public class NewChaptersActivity extends BaseAppActivity { - - //views - private RecyclerView mRecyclerView; - private TextView mTextViewHolder; - private ProgressBar mProgressBar; - //utils - private final MangaList mList = new MangaList(); - private final NewChaptersAdapter mAdapter = new NewChaptersAdapter(mList); - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_updates); - setSupportActionBar(R.id.toolbar); - enableHomeAsUp(); - - mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); - mTextViewHolder = (TextView) findViewById(R.id.textView_holder); - mProgressBar = (ProgressBar) findViewById(R.id.progressBar); - - mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - mRecyclerView.setAdapter(mAdapter); - new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, - ItemTouchHelper.START | ItemTouchHelper.END) { - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - final int pos = viewHolder.getAdapterPosition(); - final MangaInfo o = mList.get(pos); - NewChaptersProvider.getInstance(NewChaptersActivity.this) - .markAsViewed(o.hashCode()); - mList.remove(pos); - mAdapter.notifyItemRemoved(pos); - mTextViewHolder.setVisibility(mList.size() == 0 ? View.VISIBLE : View.GONE); - } - }).attachToRecyclerView(mRecyclerView); - - if (NetworkUtils.checkConnection(this)) { - new LoadTask(this).attach(this).start(); - } else { - mTextViewHolder.setText(R.string.no_network_connection); - AnimUtils.crossfade(mProgressBar, mTextViewHolder); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.updates, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_checkall: - new AlertDialog.Builder(this) - .setMessage(R.string.mark_all_viewed_confirm) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - NewChaptersProvider.getInstance(NewChaptersActivity.this) - .markAllAsViewed(); - mList.clear(); - mAdapter.notifyDataSetChanged(); - mTextViewHolder.setVisibility(View.VISIBLE); - } - }).create().show(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - private static class LoadTask extends WeakAsyncTask { - - LoadTask(NewChaptersActivity object) { - super(object); - } - - @Override - protected void onPreExecute(@NonNull NewChaptersActivity object) { - object.mProgressBar.setVisibility(View.VISIBLE); - } - - - @Override - protected MangaList doInBackground(Void... params) { - try { - final FavouritesProvider favs = FavouritesProvider.getInstance(getObject()); - final NewChaptersProvider news = NewChaptersProvider.getInstance(getObject()); - news.checkForNewChapters(); - MangaList mangas = favs.getList(0, 0, 0); - Map updates = news.getLastUpdates(); - Integer t; - final MangaList res = new MangaList(); - for (MangaInfo o:mangas) { - t = updates.get(o.hashCode()); - if (t != null && t != 0) { - o.extra = "+" + t; - res.add(o); - } - } - return res; - } catch (IOException e) { - FileLogger.getInstance().report("CHUPD", e); - return null; - } - } - - @Override - protected void onPostExecute(@NonNull NewChaptersActivity activity, MangaList mangaInfos) { - if (mangaInfos == null) { - AnimUtils.crossfade(activity.mProgressBar, activity.mTextViewHolder); - Toast.makeText(activity, R.string.error, Toast.LENGTH_SHORT).show(); - } else { - if (mangaInfos.isEmpty()) { - AnimUtils.crossfade(activity.mProgressBar, activity.mTextViewHolder); - } else { - activity.mList.clear(); - activity.mList.addAll(mangaInfos); - activity.mAdapter.notifyDataSetChanged(); - AnimUtils.crossfade(activity.mProgressBar, activity.mRecyclerView); - } - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/PreviewActivity2.java b/app/src/main/java/org/nv95/openmanga/activities/PreviewActivity2.java deleted file mode 100644 index bd92a30b..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/PreviewActivity2.java +++ /dev/null @@ -1,516 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.annotation.SuppressLint; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.Snackbar; -import android.support.design.widget.TabLayout; -import android.support.v4.view.ViewPager; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.BookmarksAdapter; -import org.nv95.openmanga.adapters.ChaptersAdapter; -import org.nv95.openmanga.adapters.OnChapterClickListener; -import org.nv95.openmanga.adapters.SimpleViewPagerAdapter; -import org.nv95.openmanga.dialogs.ChaptersSelectDialog; -import org.nv95.openmanga.dialogs.MenuDialog; -import org.nv95.openmanga.helpers.ContentShareHelper; -import org.nv95.openmanga.helpers.MangaSaveHelper; -import org.nv95.openmanga.items.Bookmark; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.ChaptersList; -import org.nv95.openmanga.providers.BookmarksProvider; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.NewChaptersProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.services.ExportService; -import org.nv95.openmanga.utils.AnimUtils; -import org.nv95.openmanga.utils.ChangesObserver; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.MangaStore; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.ProgressAsyncTask; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.util.Collections; -import java.util.List; - -import static org.nv95.openmanga.R.string.bookmarks; -import static org.nv95.openmanga.R.string.description; - -/** - * Created by unravel22 on 18.02.17. - */ - -public class PreviewActivity2 extends BaseAppActivity implements BookmarksAdapter.OnBookmarkClickListener, - OnChapterClickListener, AppBarLayout.OnOffsetChangedListener, ChangesObserver.OnMangaChangesListener { - - private MangaSummary mManga; - private boolean mToolbarCollapsed = false; - - private TabLayout mTabLayout; - private ImageView mImageView; - private RecyclerView mRecyclerViewChapters; - private RecyclerView mRecyclerViewBookmarks; - private TextView mTextViewChaptersHolder; - private TextView mTextViewBookmarksHolder; - private TextView mTextViewSummary; - private TextView mTextViewDescription; - private TextView mTextViewState; - private TextView mTextViewTitle; - private ProgressBar mProgressBar; - private ViewPager mViewPager; - private Toolbar mToolbarMenu; - - private SimpleViewPagerAdapter mPagerAdapter; - private ChaptersAdapter mChaptersAdapter; - - @SuppressLint("CutPasteId") - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_preview2); - setSupportActionBar(R.id.toolbar); - enableHomeAsUp(); - disableTitle(); - - mImageView = findViewById(R.id.imageView); - mTabLayout = findViewById(R.id.tabs); - mTextViewSummary = findViewById(R.id.textView_summary); - mTextViewTitle = findViewById(R.id.textView_title); - mProgressBar = findViewById(R.id.progressBar); - mTextViewState = findViewById(R.id.textView_state); - mViewPager = findViewById(R.id.pager); - mToolbarMenu = findViewById(R.id.toolbarMenu); - AppBarLayout appBar = findViewById(R.id.appbar_container); - if (appBar != null) { - appBar.addOnOffsetChangedListener(this); - } - mPagerAdapter = new SimpleViewPagerAdapter(); - // - View page = LayoutInflater.from(this).inflate(R.layout.page_text, mViewPager, false); - mTextViewDescription = page.findViewById(R.id.textView); - mPagerAdapter.addView(page, getString(description)); - // - page = LayoutInflater.from(this).inflate(R.layout.page_list, mViewPager, false); - mRecyclerViewChapters = page.findViewById(R.id.recyclerView); - mTextViewChaptersHolder = page.findViewById(R.id.textView_holder); - mRecyclerViewChapters.setLayoutManager(new LinearLayoutManager(this)); - mTextViewChaptersHolder.setText(R.string.no_chapters_found); - mPagerAdapter.addView(page, getString(R.string.chapters)); - // - page = LayoutInflater.from(this).inflate(R.layout.page_list, mViewPager, false); - mRecyclerViewBookmarks = page.findViewById(R.id.recyclerView); - mTextViewBookmarksHolder = page.findViewById(R.id.textView_holder); - mRecyclerViewBookmarks.setLayoutManager(new LinearLayoutManager(this)); - mTextViewBookmarksHolder.setText(R.string.no_bookmarks_tip); - mPagerAdapter.addView(page, getString(bookmarks)); - - mViewPager.setAdapter(mPagerAdapter); - mTabLayout.setupWithViewPager(mViewPager); - mChaptersAdapter = new ChaptersAdapter(this); - mChaptersAdapter.setOnItemClickListener(this); - mRecyclerViewChapters.setAdapter(mChaptersAdapter); - mToolbarMenu.inflateMenu(R.menu.toolbar_actions); - mToolbarMenu.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - return PreviewActivity2.this.onOptionsItemSelected(item); - } - }); - - MangaInfo mangaInfo = new MangaInfo(getIntent().getExtras()); - if (mangaInfo.provider != LocalMangaProvider.class && !NetworkUtils.checkConnection(this)) { - mManga = LocalMangaProvider.getInstance(this).getLocalManga(mangaInfo); - Snackbar.make(mViewPager, R.string.no_network_connection, Snackbar.LENGTH_SHORT).show(); - } else { - mManga = new MangaSummary(mangaInfo); - } - ImageUtils.setImage(mImageView, mManga.preview); - mTextViewTitle.setText(mManga.name); - mTextViewSummary.setText(mManga.genres); - mViewPager.setCurrentItem(HistoryProvider.getInstance(this).has(mManga) ? 1 : 0, false); - switch (LocalMangaProvider.class.equals(mManga.provider) ? MangaInfo.STATUS_UNKNOWN : mManga.status) { - case MangaInfo.STATUS_COMPLETED: - mTextViewState.setText(R.string.status_completed); - break; - case MangaInfo.STATUS_ONGOING: - mTextViewState.setText(R.string.status_ongoing); - break; - default: - mTextViewState.setVisibility(View.GONE); - } - invalidateMenuBar(); - ChangesObserver.getInstance().addListener(this); - new LoadTask(this).attach(this).start(); - - new ContentShareHelper(this).buildOpenWithSubmenu(mManga, - mToolbarMenu.getMenu().findItem(R.id.action_open_ext)); - } - - @Override - protected void onDestroy() { - ChangesObserver.getInstance().removeListener(this); - super.onDestroy(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.preview2, menu); - new ContentShareHelper(this).buildOpenWithSubmenu(mManga, - menu.findItem(R.id.action_open_ext)); - return super.onCreateOptionsMenu(menu); - } - - public void invalidateMenuBar() { - if (mToolbarCollapsed) { - return; - } - Menu menu = mToolbarMenu.getMenu(); - boolean isLocal = LocalMangaProvider.class.equals(mManga.provider); - menu.findItem(R.id.action_save).setVisible(!isLocal); - menu.findItem(R.id.action_remove).setVisible(isLocal); - menu.findItem(R.id.action_export).setVisible(isLocal); - menu.findItem(R.id.action_sort).setIcon(mChaptersAdapter.isReversed() ? R.drawable.ic_sort_ascending_white : R.drawable.ic_sort_descending_white); - menu.findItem(R.id.action_save_more).setVisible(isLocal && mManga.status == MangaInfo.STATUS_ONGOING); - if (isLocal) { - menu.findItem(R.id.action_favourite).setVisible(false); - } else if (FavouritesProvider.getInstance(this).has(mManga)) { - menu.findItem(R.id.action_favourite).setIcon(R.drawable.ic_favorite_light); - menu.findItem(R.id.action_favourite).setTitle(R.string.action_unfavourite); - } else { - menu.findItem(R.id.action_favourite).setIcon(R.drawable.ic_favorite_outline_light); - menu.findItem(R.id.action_favourite).setTitle(R.string.action_favourite); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (mToolbarCollapsed) { - menu.setGroupVisible(R.id.group_all, true); - boolean isLocal = LocalMangaProvider.class.equals(mManga.provider); - menu.findItem(R.id.action_save).setVisible(!isLocal); - menu.findItem(R.id.action_remove).setVisible(isLocal); - menu.findItem(R.id.action_export).setVisible(isLocal); - menu.findItem(R.id.action_save_more).setVisible(isLocal && mManga.status == MangaInfo.STATUS_ONGOING); - if (isLocal) { - menu.findItem(R.id.action_favourite).setVisible(false); - } else if (FavouritesProvider.getInstance(this).has(mManga)) { - menu.findItem(R.id.action_favourite).setTitle(R.string.action_unfavourite); - } else { - menu.findItem(R.id.action_favourite).setTitle(R.string.action_favourite); - } - } else { - menu.setGroupVisible(R.id.group_all, false); - } - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_favourite: - final FavouritesProvider favouritesProvider = FavouritesProvider.getInstance(this); - FavouritesProvider.dialog(this, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_NEUTRAL) { - if (favouritesProvider.remove(mManga)) { - ChangesObserver.getInstance().emitOnFavouritesChanged(mManga, -1); - Snackbar.make(mViewPager, R.string.unfavourited, Snackbar.LENGTH_SHORT).show(); - } - } else { - NewChaptersProvider.getInstance(PreviewActivity2.this) - .storeChaptersCount(mManga.id, mManga.getChapters().size()); - ChangesObserver.getInstance().emitOnFavouritesChanged(mManga, which); - Snackbar.make(mViewPager, R.string.favourited, Snackbar.LENGTH_SHORT).show(); - } - } - }, mManga); - return true; - case R.id.action_save: - if (mManga.chapters.size() != 0) { - new MangaSaveHelper(this).confirmSave(mManga); - } - return true; - case R.id.action_share: - new ContentShareHelper(this).share(mManga); - return true; - case R.id.action_sort: - mChaptersAdapter.reverse(); - item.setIcon(mChaptersAdapter.isReversed() ? R.drawable.ic_sort_ascending_white : R.drawable.ic_sort_descending_white); - return true; - case R.id.action_relative: - startActivity(new Intent(this, SearchActivity.class) - .putExtra("query", mManga.name)); - return true; - case R.id.action_shortcut: - new ContentShareHelper(this).createShortcut(mManga); - return true; - case R.id.action_export: - ExportService.start(this, mManga); - return true; - case R.id.action_remove: - deleteDialog(); - return true; - case R.id.action_save_more: - if (checkConnectionWithSnackbar(mTextViewDescription)) { - new LoadSourceTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mManga); - } - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onBookmarkSelected(Bookmark bookmark) { - HistoryProvider.getInstance(this).add(mManga, bookmark.chapter, 0); - startActivity(new Intent(this, ReadActivity2.class).putExtra("chapter", bookmark.chapter).putExtra("page", bookmark.page).putExtras(mManga.toBundle())); - } - - private void deleteDialog() { - new ChaptersSelectDialog(this) - .showRemove(mManga, new ChaptersSelectDialog.OnChaptersRemoveListener() { - @Override - public void onChaptersRemove(@Nullable long[] ids) { - if (ids == null) { - if (new MangaStore(PreviewActivity2.this).dropMangas(new long[]{mManga.id})) { - HistoryProvider.getInstance(PreviewActivity2.this).remove(new long[]{mManga.id}); - ChangesObserver.getInstance().emitOnLocalChanged(mManga.id, null); - finish(); - } - } else { - if (new MangaStore(PreviewActivity2.this).dropChapters(mManga.id, ids)) { - Snackbar.make(mTextViewDescription, getString(R.string.chapters_removed, ids.length), Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(mTextViewDescription, R.string.error, Snackbar.LENGTH_SHORT).show(); - } - new LoadTask(PreviewActivity2.this).attach(PreviewActivity2.this).start(); - } - } - }); - } - - @Override - public void onChapterClick(int pos, MangaChapter chapter) { - if (pos == -1) { - Intent intent = new Intent(this, ReadActivity2.class); - intent.putExtras(mManga.toBundle()); - HistoryProvider.HistorySummary hs = HistoryProvider.getInstance(this).get(mManga); - if (hs != null) { - int index = mManga.chapters.indexByNumber(hs.getChapter()); - if (index != -1) { - intent.putExtra("chapter", index); - intent.putExtra("page", hs.getPage()); - } - } - startActivity(intent); - } else { - if (mChaptersAdapter.isReversed()) pos = mManga.chapters.size() - pos - 1; - HistoryProvider.getInstance(this).add(mManga, chapter.number, 0); - startActivity(new Intent(this, ReadActivity2.class).putExtra("chapter", pos).putExtras(mManga.toBundle())); - } - } - - @Override - public boolean onChapterLongClick(int pos, MangaChapter chapter) { - if (pos == -1 || mManga.provider == LocalMangaProvider.class) { - return false; - } else { - new MenuDialog(this, R.menu.chapter, chapter.name) - .setOnItemClickListener(new ChapterMenuListener(chapter)) - .show(); - return true; - } - } - - @Override - protected void onResume() { - super.onResume(); - if (mChaptersAdapter.getItemCount() != 0) { - mChaptersAdapter.setExtra(HistoryProvider.getInstance(PreviewActivity2.this).get(mManga)); - mChaptersAdapter.notifyDataSetChanged(); - } - } - - @Override - public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { - if (verticalOffset <= appBarLayout.getTotalScrollRange() / -2) { - if (!mToolbarCollapsed) { - mToolbarCollapsed = true; - invalidateOptionsMenu(); - } - } else { - if (mToolbarCollapsed) { - mToolbarCollapsed = false; - invalidateOptionsMenu(); - invalidateMenuBar(); - } - } - } - - @Override - public void onLocalChanged(int id, @Nullable MangaInfo manga) { - if (id == mManga.id && manga == null) { - finish(); - } - } - - @Override - public void onFavouritesChanged(@NonNull MangaInfo manga, int category) { - invalidateMenuBar(); - invalidateOptionsMenu(); - } - - @Override - public void onHistoryChanged(@NonNull MangaInfo manga) { - - } - - private static class LoadTask extends WeakAsyncTask, MangaSummary> { - - LoadTask(PreviewActivity2 object) { - super(object); - } - - @Override - protected void onPreExecute(@NonNull PreviewActivity2 activity) { - AnimUtils.crossfade(null, activity.mProgressBar); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected MangaSummary doInBackground(Void... params) { - try { - //noinspection unchecked - publishProgress(BookmarksProvider.getInstance(getObject()).getAll(getObject().mManga.id)); - MangaProvider provider = MangaProviderManager.instanceProvider(getObject(), getObject().mManga.provider); - return provider.getDetailedInfo(getObject().mManga); - } catch (Exception e) { - return null; - } - } - - @Override - protected void onProgressUpdate(@NonNull PreviewActivity2 activity, List[] values) { - activity.mRecyclerViewBookmarks.setAdapter(new BookmarksAdapter(values[0], activity)); - if (values[0].isEmpty()) { - activity.mTextViewBookmarksHolder.setText(R.string.no_bookmarks_tip); - activity.mTextViewBookmarksHolder.setVisibility(View.VISIBLE); - } - } - - @Override - protected void onPostExecute(@NonNull PreviewActivity2 activity, MangaSummary mangaSummary) { - if (mangaSummary != null) { - activity.mManga = mangaSummary; - activity.invalidateOptionsMenu(); - activity.invalidateMenuBar(); - activity.mTextViewSummary.setText(activity.mManga.genres); - activity.mTextViewDescription.setText(activity.mManga.description); - ImageUtils.updateImage(activity.mImageView, activity.mManga.preview); - activity.mChaptersAdapter.setData(activity.mManga.chapters); - activity.mChaptersAdapter.setExtra(HistoryProvider.getInstance(activity).get(activity.mManga)); - activity.mChaptersAdapter.notifyDataSetChanged(); - if (mangaSummary.chapters.isEmpty()) { - activity.mTextViewChaptersHolder.setText(R.string.no_chapters_found); - AnimUtils.crossfade(activity.mProgressBar, activity.mTextViewChaptersHolder); - } else { - AnimUtils.crossfade(activity.mProgressBar, null); - if (!activity.showcase(activity.mToolbarMenu.findViewById(R.id.action_favourite), R.string.action_favourite, R.string.tip_favourite)) { - if (LocalMangaProvider.class.equals(activity.mManga.provider)) { - activity.showcase(activity.mToolbarMenu.findViewById(R.id.action_save_more), R.string.action_save_add, R.string.tip_save_more); - } else { - activity.showcase(activity.mToolbarMenu.findViewById(R.id.action_save), R.string.save_manga, R.string.tip_save); - } - } - } - } else { - activity.mTextViewChaptersHolder.setText(R.string.loading_error); - AnimUtils.crossfade(activity.mProgressBar, activity.mTextViewChaptersHolder); - activity.mTextViewDescription.setText(R.string.loading_error); - } - } - } - - private static class LoadSourceTask extends ProgressAsyncTask implements DialogInterface.OnCancelListener { - - LoadSourceTask(PreviewActivity2 object) { - super(object); - } - - @Override - protected MangaSummary doInBackground(MangaInfo... params) { - return LocalMangaProvider.getInstance(getActivity()) - .getSource(params[0]); - } - - @Override - protected void onPostExecute(@NonNull BaseAppActivity activity, MangaSummary sourceManga) { - PreviewActivity2 a = (PreviewActivity2) activity; - if (sourceManga == null) { - Snackbar.make(a.mViewPager, R.string.loading_error, Snackbar.LENGTH_SHORT) - .show(); - return; - } - ChaptersList newChapters = sourceManga.chapters.complementByName(a.mManga.chapters); - if (sourceManga.chapters.size() <= a.mManga.chapters.size()) { - Snackbar.make(a.mViewPager, R.string.no_new_chapters, Snackbar.LENGTH_SHORT) - .show(); - } else { - sourceManga.chapters = newChapters; - new MangaSaveHelper(a).confirmSave(sourceManga, R.string.action_save_add); - } - } - } - - private class ChapterMenuListener implements MenuItem.OnMenuItemClickListener { - - private final MangaChapter mChapter; - - ChapterMenuListener(MangaChapter chapter) { - mChapter = chapter; - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_save: - new MangaSaveHelper(PreviewActivity2.this) - .save(mManga, mChapter); - return true; - case R.id.action_save_prev: - new MangaSaveHelper(PreviewActivity2.this) - .save(mManga, mManga.chapters.first(), mChapter); - return true; - case R.id.action_save_next: - new MangaSaveHelper(PreviewActivity2.this) - .save(mManga, mChapter, mManga.chapters.last()); - return true; - default: - return false; - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/ReadActivity2.java b/app/src/main/java/org/nv95/openmanga/activities/ReadActivity2.java deleted file mode 100644 index 6f6770e4..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/ReadActivity2.java +++ /dev/null @@ -1,728 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Point; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.settings.SettingsActivity2; -import org.nv95.openmanga.components.ReaderMenu; -import org.nv95.openmanga.components.reader.MangaReader; -import org.nv95.openmanga.components.reader.OnOverScrollListener; -import org.nv95.openmanga.components.reader.PageWrapper; -import org.nv95.openmanga.components.reader.StandardMangaReader; -import org.nv95.openmanga.components.reader.webtoon.WebtoonReader; -import org.nv95.openmanga.dialogs.HintDialog; -import org.nv95.openmanga.dialogs.NavigationListener; -import org.nv95.openmanga.dialogs.ThumbnailsDialog; -import org.nv95.openmanga.helpers.BrightnessHelper; -import org.nv95.openmanga.helpers.ContentShareHelper; -import org.nv95.openmanga.helpers.MangaSaveHelper; -import org.nv95.openmanga.helpers.PermissionsHelper; -import org.nv95.openmanga.helpers.ReaderConfig; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.items.SimpleDownload; -import org.nv95.openmanga.lists.ChaptersList; -import org.nv95.openmanga.providers.BookmarksProvider; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.utils.ChangesObserver; -import org.nv95.openmanga.utils.InternalLinkMovement; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.StorageUtils; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.io.File; -import java.util.List; - -/** - * Created by nv95 on 16.11.16. - */ - -public class ReadActivity2 extends BaseAppActivity implements View.OnClickListener, ReaderMenu.Callback, - OnOverScrollListener, NavigationListener, InternalLinkMovement.OnLinkClickListener { - - private static final int REQUEST_SETTINGS = 1299; - - private FrameLayout mProgressFrame; - private MangaReader mReader; - private ReaderMenu mMenuPanel; - private ImageView mMenuButton; - private FrameLayout mOverScrollFrame; - private ImageView mOverScrollArrow; - private TextView mOverScrollText; - - private MangaSummary mManga; - private int mChapter; - private ReaderConfig mConfig; - private BrightnessHelper mBrightnessHelper; - @NonNull - private final Point mViewport = new Point(1, 1); - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_reader2); - mProgressFrame = findViewById(R.id.loader); - mMenuPanel = findViewById(R.id.menuPanel); - mReader = findViewById(R.id.reader); - mMenuButton = findViewById(R.id.imageView_menu); - mOverScrollFrame = findViewById(R.id.overscrollFrame); - mOverScrollArrow = findViewById(R.id.imageView_arrow); - mOverScrollText = findViewById(R.id.textView_title); - - if (isDarkTheme()) { - mMenuButton.setColorFilter(ContextCompat.getColor(this, R.color.white_overlay_85)); - } - mMenuButton.setOnClickListener(this); - - mBrightnessHelper = new BrightnessHelper(getWindow()); - mReader.initAdapter(this, this); - mReader.addOnPageChangedListener(mMenuPanel); - mReader.setOnOverScrollListener(this); - - Bundle extras = savedInstanceState != null ? savedInstanceState : getIntent().getExtras(); - mManga = new MangaSummary(extras); - mChapter = extras.getInt("chapter", 0); - int page = extras.getInt("page", 0); - - mMenuPanel.setData(mManga); - mMenuPanel.setCallback(this); - if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT) { - mMenuPanel.setFitsSystemWindows(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Window window = getWindow(); - int color = ContextCompat.getColor(this, R.color.transparent_dark); - window.setStatusBarColor(color); - window.setNavigationBarColor(color); - } - } - updateConfig(); - new ChapterLoadTask(this, page).attach(this).start(mManga.getChapters().get(mChapter)); - } - - @Override - protected void onPostResume() { - super.onPostResume(); - DisplayMetrics dm = getResources().getDisplayMetrics(); - mViewport.set(dm.widthPixels, dm.heightPixels); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus && Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT && !mMenuPanel.isVisible()) { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - } - - @Override - protected void onPause() { - saveHistory(); - super.onPause(); - } - - @Override - protected void onDestroy() { - mReader.finish(); - ChangesObserver.getInstance().emitOnHistoryChanged(mManga); - super.onDestroy(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putAll(mManga.toBundle()); - outState.putInt("page", mReader.getCurrentPosition()); - outState.putInt("chapter", mChapter); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case REQUEST_SETTINGS: - int pageIndex = mReader.getCurrentPosition(); - updateConfig(); - mReader.getLoader().setEnabled(false); - mReader.scrollToPosition(pageIndex); - mReader.getLoader().setEnabled(true); - mReader.notifyDataSetChanged(); - break; - case PermissionsHelper.REQUEST_CODE: - if (resultCode == Activity.RESULT_OK) { - new ImageSaveTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, - mReader.getItem(mReader.getCurrentPosition())); - } else { - Snackbar.make((View) mReader, R.string.dir_no_access, Snackbar.LENGTH_SHORT).show(); - } - break; - } - } - - private void saveHistory() { - if (mChapter >= 0 && mChapter < mManga.chapters.size()) { - HistoryProvider.getInstance(this).add(mManga, mManga.chapters.get(mChapter).number, mReader.getCurrentPosition()); - } - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.imageView_menu: - mMenuPanel.show(); - break; - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_MENU) { - if (mProgressFrame.getVisibility() != View.VISIBLE) { - if (mMenuPanel.isShown()) { - mMenuPanel.hide(); - } else { - mMenuPanel.show(); - } - } - return super.onKeyDown(keyCode, event); - } - if (mConfig.scrollByVolumeKeys) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - if (!mReader.scrollToNext(true)) { - if (mChapter < mManga.getChapters().size() - 1) { - mChapter++; - Toast t = Toast.makeText(this, mManga.getChapters().get(mChapter).name, Toast.LENGTH_SHORT); - t.setGravity(Gravity.TOP, 0, 0); - t.show(); - new ChapterLoadTask(ReadActivity2.this,0) - .attach(ReadActivity2.this) - .start(mManga.getChapters().get(mChapter)); - return true; - } - } else { - return true; - } - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - if (!mReader.scrollToPrevious(true)) { - if (mChapter > 0) { - mChapter--; - Toast t = Toast.makeText(this, mManga.getChapters().get(mChapter).name, Toast.LENGTH_SHORT); - t.setGravity(Gravity.TOP, 0, 0); - t.show(); - new ChapterLoadTask(ReadActivity2.this, -1) - .attach(ReadActivity2.this) - .start(mManga.getChapters().get(mChapter)); - return true; - } - } else { - return true; - } - } - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return mConfig.scrollByVolumeKeys && - (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) || - super.onKeyUp(keyCode, event); - } - - private void updateConfig() { - boolean isWeb = HistoryProvider.getInstance(this).isWebMode(mManga); - if (isWeb) { - if (mReader instanceof StandardMangaReader) { - StandardMangaReader oldReader = (StandardMangaReader) mReader; - ViewGroup parent = (ViewGroup) ((View) mReader).getParent(); - parent.removeView((View) mReader); - mReader = new WebtoonReader(this); - mReader.initAdapter(this, this); - mReader.setPages(oldReader.getPages()); - mReader.scrollToPosition(oldReader.getCurrentPosition()); - mReader.addOnPageChangedListener(mMenuPanel); - mReader.setOnOverScrollListener(this); - ((View) mReader).setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - parent.addView((View) mReader, 0); - } - } else { - if (mReader instanceof WebtoonReader) { - WebtoonReader oldReader = (WebtoonReader) mReader; - ViewGroup parent = (ViewGroup) ((View) mReader).getParent(); - parent.removeView((View) mReader); - mReader = new StandardMangaReader(this); - mReader.initAdapter(this, this); - mReader.setPages(oldReader.getPages()); - mReader.scrollToPosition(oldReader.getCurrentPosition()); - mReader.addOnPageChangedListener(mMenuPanel); - mReader.setOnOverScrollListener(this); - ((View) mReader).setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - parent.addView((View) mReader, 0); - } - } - mConfig = ReaderConfig.load(this, mManga); - mReader.applyConfig( - mConfig.scrollDirection == ReaderConfig.DIRECTION_VERTICAL, - mConfig.scrollDirection == ReaderConfig.DIRECTION_REVERSED, - mConfig.mode == ReaderConfig.MODE_PAGES, - mConfig.showNumbers - ); - if (mConfig.keepScreenOn) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } else { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - if (mConfig.hideMenuButton) { - mMenuButton.setImageDrawable(null); - } else { - mMenuButton.setImageResource(R.drawable.ic_action_navigation_more_vert); - } - mReader.getLoader().setPreloadEnabled(mConfig.preload == ReaderConfig.PRELOAD_ALWAYS - || (mConfig.preload == ReaderConfig.PRELOAD_WLAN_ONLY && MangaProviderManager.isWlan(this))); - mReader.setScaleMode(mConfig.scaleMode); - if (mConfig.adjustBrightness) { - mBrightnessHelper.setBrightness(mConfig.brightnessValue); - } else { - mBrightnessHelper.reset(); - } - mReader.setTapNavs(mConfig.tapNavs); - } - - @Override - public void onActionClick(int id) { - final int pos = mReader.getCurrentPosition(); - switch (id) { - case android.R.id.home: - finish(); - break; - case R.id.progressBar: - case android.R.id.title: - showChaptersList(); - break; - case R.id.action_save: - new MangaSaveHelper(this).confirmSave(mManga); - break; - case R.id.action_save_more: - new LoadSourceTask(this).attach(this).start(mManga); - break; - case R.id.action_save_image: - if (PermissionsHelper.accessCommonDir(this, Environment.DIRECTORY_PICTURES)) { - new ImageSaveTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mReader.getItem(pos)); - } - break; - case R.id.menuitem_thumblist: - new ThumbnailsDialog(this, mReader.getLoader()) - .setNavigationListener(this) - .show(pos); - break; - case R.id.action_webmode: - updateConfig(); - HintDialog.showOnce(this, R.string.tip_webtoon); - break; - case R.id.nav_action_settings: - SettingsActivity2.openReaderSettings(this, REQUEST_SETTINGS); - break; - case R.id.menuitem_bookmark: - mMenuPanel.onBookmarkAdded( - BookmarksProvider.getInstance(this) - .add(mManga, mManga.chapters.get(mChapter).number, pos, mReader.getItem(pos).getFilename()) - ); - LayoutUtils.centeredToast(this, R.string.bookmark_added); - break; - case R.id.menuitem_unbookmark: - if (BookmarksProvider.getInstance(this) - .remove(mManga, mManga.chapters.get(mChapter).number, pos)) { - mMenuPanel.onBookmarkRemoved(pos); - LayoutUtils.centeredToast(this, R.string.bookmark_removed); - } - break; - case R.id.menuitem_rotation: - int orientation = getResources().getConfiguration().orientation; - setRequestedOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE ? - ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); - break; - /*case R.id.nav_left: - case R.id.nav_right: - int rd = getRealDirection(id == R.id.nav_left ? OnOverScrollListener.LEFT : OnOverScrollListener.RIGHT); - if (rd == -1) { - if (!mReader.scrollToPrevious(true)) { - if (mChapter > 0) { - mChapter--; - Toast t = Toast.makeText(this, mManga.getChapters().get(mChapter).name, Toast.LENGTH_SHORT); - t.setGravity(Gravity.TOP, 0, 0); - t.show(); - new ChapterLoadTask(-1).startLoading(mManga.getChapters().get(mChapter)); - } - } - } else { - if (!mReader.scrollToNext(true)) { - if (mChapter < mManga.getChapters().size() - 1) { - mChapter++; - Toast t = Toast.makeText(this, mManga.getChapters().get(mChapter).name, Toast.LENGTH_SHORT); - t.setGravity(Gravity.TOP, 0, 0); - t.show(); - new ChapterLoadTask(0).startLoading(mManga.getChapters().get(mChapter)); - } - } - } - break;*/ - } - } - - @Override - public void onPageChanged(int index) { - mReader.scrollToPosition(index); - } - - @Override - public void onVisibilityChanged(boolean visible) { - mMenuButton.setVisibility(visible ? View.INVISIBLE : View.VISIBLE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - View decorView = getWindow().getDecorView(); - if (visible) { - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } else { - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // прячем панель навигации - | View.SYSTEM_UI_FLAG_FULLSCREEN // прячем строку состояния - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - } - } - - - private void showChaptersList() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setSingleChoiceItems(mManga.getChapters().getNames(), mChapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mMenuPanel.hide(); - mChapter = which; - new ChapterLoadTask(ReadActivity2.this,0) - .attach(ReadActivity2.this) - .start(mManga.getChapters().get(mChapter)); - dialog.dismiss(); - } - }); - builder.setTitle(R.string.chapters_list); - builder.create().show(); - } - - @Override - public void onOverScrollFlying(int direction, int distance) { - float d = distance / (float)(direction <= 1 ? mViewport.x : mViewport.y); - mOverScrollFrame.setAlpha(Math.min(1f, d * 3.f)); - } - - @Override - public boolean onOverScrollFinished(int direction, int distance) { - float d = distance / (float)(direction <= 1 ? mViewport.x : mViewport.y); - if (d >= 0.3f) { - return true; - } else { - mOverScrollFrame.setVisibility(View.GONE); - return false; - } - } - - @SuppressLint("RtlHardcoded") - @Override - public void onOverScrollStarted(int direction) { - if (getRealDirection(direction) == -1) { - //prev chapter - if (mChapter > 0) { - mOverScrollText.setText(getString(R.string.prev_chapter, mManga.getChapters().get(mChapter - 1).name)); - } else { - return; - } - } else { - //next chapter - if (mChapter < mManga.getChapters().size() - 1) { - mOverScrollText.setText(getString(R.string.next_chapter, mManga.getChapters().get(mChapter + 1).name)); - } else { - return; - } - } - mOverScrollFrame.setAlpha(0f); - mOverScrollFrame.setVisibility(View.VISIBLE); - if (direction == TOP) { - ((FrameLayout.LayoutParams)mOverScrollText.getLayoutParams()).gravity = Gravity.CENTER; - ((FrameLayout.LayoutParams)mOverScrollArrow.getLayoutParams()).gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - mOverScrollArrow.setRotation(0f); - } else if (direction == LEFT) { - ((FrameLayout.LayoutParams)mOverScrollText.getLayoutParams()).gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - ((FrameLayout.LayoutParams)mOverScrollArrow.getLayoutParams()).gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; - mOverScrollArrow.setRotation(-90f); - } else if (direction == BOTTOM) { - ((FrameLayout.LayoutParams)mOverScrollText.getLayoutParams()).gravity = Gravity.CENTER; - ((FrameLayout.LayoutParams)mOverScrollArrow.getLayoutParams()).gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - mOverScrollArrow.setRotation(180f); - } else if (direction == RIGHT) { - ((FrameLayout.LayoutParams)mOverScrollText.getLayoutParams()).gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - ((FrameLayout.LayoutParams)mOverScrollArrow.getLayoutParams()).gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; - mOverScrollArrow.setRotation(90f); - } - } - - @Override - public void onOverScrolled(int direction) { - if (mOverScrollFrame.getVisibility() != View.VISIBLE) { - return; - } - mOverScrollFrame.setVisibility(View.GONE); - int rd = getRealDirection(direction); - mChapter += rd; - new ChapterLoadTask(this, rd == -1 ? -1 : 0) - .attach(this) - .start(mManga.getChapters().get(mChapter)); - } - - private int getRealDirection(int direction) { - return direction == TOP || direction == LEFT ? mReader.isReversed() ? 1 : -1 : mReader.isReversed() ? -1 : 1; - } - - @Override - public void onPageChange(int page) { - mReader.scrollToPosition(page); - } - - @Override - public void onLinkClicked(TextView view, String scheme, String url) { - switch (scheme) { - case "app": - switch (url) { - case "retry": - mReader.reload(mReader.getCurrentPosition()); - break; - } - break; - } - } - - private static class ChapterLoadTask extends WeakAsyncTask> implements DialogInterface.OnCancelListener { - - private final int mPageIndex; - - ChapterLoadTask(ReadActivity2 object, int page) { - super(object); - mPageIndex = page; - } - - @Override - protected void onPreExecute(@NonNull ReadActivity2 a) { - a.mProgressFrame.setVisibility(View.VISIBLE); - a.mReader.getLoader().cancelAll(); - a.mReader.getLoader().setEnabled(false); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected List doInBackground(MangaChapter... mangaChapters) { - try { - MangaProvider provider = MangaProviderManager.instanceProvider(getObject(), mangaChapters[0].provider); - return provider.getPages(mangaChapters[0].readLink); - } catch (Exception ignored) { - return null; - } - } - - private void onFailed(@NonNull final ReadActivity2 a) { - new AlertDialog.Builder(a) - .setMessage(NetworkUtils.checkConnection(a) ? R.string.loading_error : R.string.no_network_connection) - .setTitle(R.string.app_name) - .setOnCancelListener(this) - .setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new ChapterLoadTask(a, mPageIndex) - .attach(a) - .start(a.mManga.getChapters().get(a.mChapter)); - } - }) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }) - .create() - .show(); - } - - @Override - protected void onPostExecute(@NonNull ReadActivity2 a, List mangaPages) { - if (mangaPages == null) { - onFailed(a); - return; - } - a.mReader.setPages(mangaPages); - a.mReader.notifyDataSetChanged(); - int pos = mPageIndex == -1 ? a.mReader.getItemCount() - 1 : mPageIndex; - a.mReader.scrollToPosition(pos); - a.mReader.getLoader().setEnabled(true); - a.mReader.notifyDataSetChanged(); - a.mMenuPanel.onChapterChanged(a.mManga.chapters.get(a.mChapter) ,mangaPages.size()); - a.mProgressFrame.setVisibility(View.GONE); - a.showcase(a.mMenuButton, R.string.menu, R.string.tip_reader_menu); - } - - @Override - public void onCancel(DialogInterface dialogInterface) { - ReadActivity2 a = getObject(); - if (a != null && a.mReader.getItemCount() == 0) { - a.finish(); - } - } - } - - private static class ImageSaveTask extends WeakAsyncTask { - - ImageSaveTask(ReadActivity2 object) { - super(object); - } - - @Override - protected void onPreExecute(@Nullable ReadActivity2 a) { - if (a != null) { - a.mProgressFrame.setVisibility(View.VISIBLE); - a.mMenuPanel.hide(); - } - } - - @SuppressWarnings("ConstantConditions") - @Override - protected File doInBackground(PageWrapper... pageWrappers) { - File dest; - try { - MangaProvider provider; - provider = MangaProviderManager.instanceProvider(getObject(), pageWrappers[0].page.provider); - String url = provider.getPageImage(pageWrappers[0].page); - if (pageWrappers[0].isLoaded()) { - //noinspection ConstantConditions - dest = new File(pageWrappers[0].getFilename()); - } else { - dest = new File(getObject().getExternalFilesDir("temp"), String.valueOf(url.hashCode())); - - final SimpleDownload dload = new SimpleDownload(url, dest); - dload.run(); - if (!dload.isSuccess()) { - return null; - } - } - File dest2 = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), String.valueOf(url.hashCode()) + ".png"); - Bitmap b = BitmapFactory.decodeFile(dest.getPath()); - if (!StorageUtils.saveBitmap(b, dest2.getPath())) { - dest2 = null; - } - b.recycle(); - return dest2; - } catch (Exception ignored) { - return null; - } - } - - @Override - protected void onPostExecute(@NonNull final ReadActivity2 a, final File file) { - a.mProgressFrame.setVisibility(View.GONE); - if (file != null && file.exists()) { - StorageUtils.scanMediaFile(a, file); - Snackbar.make(a.mMenuPanel, R.string.image_saved, Snackbar.LENGTH_LONG) - .setAction(R.string.action_share, new View.OnClickListener() { - @Override - public void onClick(View v) { - new ContentShareHelper(a).shareImage(file); - } - }) - .show(); - } else { - Snackbar.make(a.mMenuPanel, R.string.unable_to_save_image, Snackbar.LENGTH_SHORT).show(); - } - - } - } - - private static class LoadSourceTask extends WeakAsyncTask { - - LoadSourceTask(ReadActivity2 object) { - super(object); - } - - @Override - protected void onPreExecute(@NonNull ReadActivity2 a) { - a.mProgressFrame.setVisibility(View.VISIBLE); - } - - @Override - protected MangaSummary doInBackground(MangaInfo... params) { - try { - return LocalMangaProvider.getInstance(getObject()) - .getSource(params[0]); - } catch (Exception e) { - return null; - } - } - - @Override - protected void onPostExecute(@NonNull ReadActivity2 a, MangaSummary sourceManga) { - a.mProgressFrame.setVisibility(View.GONE); - if (sourceManga == null) { - Snackbar.make(a.mMenuPanel, R.string.loading_error, Snackbar.LENGTH_SHORT) - .show(); - return; - } - ChaptersList newChapters = sourceManga.chapters.complementByName(a.mManga.chapters); - if (sourceManga.chapters.size() <= a.mManga.chapters.size()) { - Snackbar.make(a.mMenuPanel, R.string.no_new_chapters, Snackbar.LENGTH_SHORT) - .show(); - } else { - sourceManga.chapters = newChapters; - new MangaSaveHelper(a).confirmSave(sourceManga, R.string.action_save_add); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/SearchActivity.java b/app/src/main/java/org/nv95/openmanga/activities/SearchActivity.java deleted file mode 100644 index a653209e..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/SearchActivity.java +++ /dev/null @@ -1,458 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.content.res.Configuration; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.text.TextUtils; -import android.view.ActionMode; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import org.jsoup.helper.StringUtil; -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.SearchHistoryAdapter; -import org.nv95.openmanga.adapters.SearchResultsAdapter; -import org.nv95.openmanga.components.SearchInput; -import org.nv95.openmanga.helpers.ContentShareHelper; -import org.nv95.openmanga.helpers.ListModeHelper; -import org.nv95.openmanga.helpers.MangaSaveHelper; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.providers.staff.ProviderSummary; -import org.nv95.openmanga.providers.staff.Providers; -import org.nv95.openmanga.utils.AnimUtils; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.WeakAsyncTask; -import org.nv95.openmanga.utils.choicecontrol.ModalChoiceCallback; -import org.nv95.openmanga.utils.choicecontrol.ModalChoiceController; - -import java.util.ArrayDeque; - -/** - * Created by nv95 on 24.12.16. - */ - -public class SearchActivity extends BaseAppActivity implements ListModeHelper.OnListModeListener, -SearchHistoryAdapter.OnHistoryEventListener, SearchResultsAdapter.OnMoreEventListener, TextView.OnEditorActionListener, - View.OnFocusChangeListener, SearchInput.OnTextChangedListener, ModalChoiceCallback { - - @Nullable - private String mQuery; - private SearchInput mSearchInput; - private RecyclerView mRecyclerView; - private TextView mTextViewHolder; - private ProgressBar mProgressBar; - private Toolbar mToolbar; - private RecyclerView mRecyclerViewSearch; - private ListModeHelper mListModeHelper; - private SearchHistoryAdapter mHistoryAdapter; - private SearchResultsAdapter mResultsAdapter; - private int mPage; - private int mStage; - private int mActiveProviderId; - private MangaProvider mCurrentProvider; - private ArrayDeque mProviders; - private MangaProviderManager mProviderManager; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_search); - mToolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(mToolbar); - enableHomeAsUp(); - setupToolbarScrolling(mToolbar); - - Bundle extras = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState; - mQuery = extras.getString("query"); - mActiveProviderId = extras.getInt("provider", -5); - - mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); - mProgressBar = (ProgressBar) findViewById(R.id.progressBar); - mTextViewHolder = (TextView) findViewById(R.id.textView_holder); - mSearchInput = (SearchInput) findViewById(R.id.searchInput); - mRecyclerViewSearch = (RecyclerView) findViewById(R.id.recyclerViewSearch); - - mRecyclerView.setLayoutManager(new GridLayoutManager(this, 1)); - mSearchInput.getEditText().setOnEditorActionListener(this); - mHistoryAdapter = new SearchHistoryAdapter(this, this); - mResultsAdapter = new SearchResultsAdapter(mRecyclerView); - mResultsAdapter.setOnLoadMoreListener(this); - mSearchInput.setOnEditFocusChangeListener(this); - mSearchInput.setOnTextChangedListener(this); - - new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - long id = viewHolder.getItemId(); - SearchHistoryAdapter.removeFromHistory(SearchActivity.this, id); - mHistoryAdapter.requery(mSearchInput.getEditText().getText().toString()); - } - }).attachToRecyclerView(mRecyclerViewSearch); - - mListModeHelper = new ListModeHelper(this, this); - mListModeHelper.applyCurrent(); - mListModeHelper.enable(); - - mResultsAdapter.getChoiceController().setCallback(this); - mResultsAdapter.getChoiceController().setEnabled(true); - - mProviderManager = new MangaProviderManager(this); - mProviders = new ArrayDeque<>(mProviderManager.getProvidersCount()); - - mRecyclerView.setAdapter(mResultsAdapter); - mRecyclerViewSearch.setAdapter(mHistoryAdapter); - mSearchInput.getEditText().setText(mQuery); - } - - @Override - protected void onPostCreate(@Nullable Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - if (TextUtils.isEmpty(mQuery)) { - LayoutUtils.showSoftKeyboard(mSearchInput.getEditText()); - } else { - closeHistory(); - search(0); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString("query", mQuery); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.search, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - mListModeHelper.onPrepareOptionsMenu(menu); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_clear: - SearchHistoryAdapter.clearHistory(this); - mHistoryAdapter.requeryAsync(null); - return true; - default: - return mListModeHelper.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); - } - } - - @Override - public void onListModeChanged(boolean grid, int sizeMode) { - int spans; - ThumbSize thumbSize; - switch (sizeMode) { - case -1: - spans = LayoutUtils.isTabletLandscape(this) ? 2 : 1; - thumbSize = ThumbSize.THUMB_SIZE_LIST; - break; - case 0: - spans = LayoutUtils.getOptimalColumnsCount(getResources(), thumbSize = ThumbSize.THUMB_SIZE_SMALL); - break; - case 1: - spans = LayoutUtils.getOptimalColumnsCount(getResources(), thumbSize = ThumbSize.THUMB_SIZE_MEDIUM); - break; - case 2: - spans = LayoutUtils.getOptimalColumnsCount(getResources(), thumbSize = ThumbSize.THUMB_SIZE_LARGE); - break; - default: - return; - } - GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); - int position = layoutManager.findFirstCompletelyVisibleItemPosition(); - layoutManager.setSpanCount(spans); - layoutManager.setSpanSizeLookup(mResultsAdapter.getSpanSizeLookup(spans)); - mResultsAdapter.setThumbnailsSize(thumbSize); - if (mResultsAdapter.setGrid(grid)) { - mRecyclerView.setAdapter(mResultsAdapter); - } - mRecyclerView.scrollToPosition(position); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mListModeHelper.applyCurrent(); - } - - @Override - protected void onDestroy() { - mListModeHelper.disable(); - super.onDestroy(); - } - - @Override - public void onHistoryItemClick(String text, boolean apply) { - mSearchInput.setText(text); - if (apply) { - mSearchInput.getEditText().onEditorAction(EditorInfo.IME_ACTION_SEARCH); - } - } - - @Override - public void onMoreButtonClick() { - search(1); - } - - @Override - public boolean onLoadMore() { - new SearchTask(this).attach(this).start(); - return true; - } - - private void search(int stage) { - ProviderSummary prov = Providers.getById(mActiveProviderId); - if (stage == 0) { - mResultsAdapter.clearData(); - AnimUtils.crossfade(mTextViewHolder, mProgressBar); - mPage = 0; - mStage = 0; - mCurrentProvider = null; - mProviders.clear(); - if (prov == null) { - mProviders.add(LocalMangaProvider.getProviderSummary(this)); - mProviders.add(HistoryProvider.getProviderSummary(this)); - mProviders.addAll(mProviderManager.getEnabledOrderedProviders()); - mStage = 1; - } else { - mProviders.add(LocalMangaProvider.getProviderSummary(this)); - mProviders.add(HistoryProvider.getProviderSummary(this)); - mProviders.add(prov); - } - new SearchTask(this).start(); - } else { - mProviders.addAll(mProviderManager.getEnabledOrderedProviders()); - if (prov != null) { - mProviders.remove(prov); - } - mPage = 0; - mCurrentProvider = null; - if (mProviders.isEmpty()) { - if (mResultsAdapter.hasItems()) { - mResultsAdapter.hideFooter(); - } else { - AnimUtils.crossfade(mProgressBar, mTextViewHolder); - } - } else { - if (mProgressBar.getVisibility() != View.VISIBLE) { - mResultsAdapter.setFooterProgress(); - } else { - mResultsAdapter.hideFooter(); - } - new SearchTask(this).attach(this).start(); - } - mStage = 1; - } - } - - @Override - public boolean onEditorAction(final TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - mQuery = v.getText().toString(); - if (StringUtil.isBlank(mQuery)) { - showToast(R.string.search_query_empty, Gravity.TOP, Toast.LENGTH_SHORT); - v.postDelayed(new Runnable() { - @Override - public void run() { - LayoutUtils.showSoftKeyboard(v); - } - }, 500); - return true; - } - SearchHistoryAdapter.addToHistory(this, mQuery); - closeHistory(); - search(0); - return true; - } else { - return false; - } - } - - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - mHistoryAdapter.requeryAsync(mQuery); - AnimUtils.crossfade(null, mRecyclerViewSearch); - setToolbarScrollingLock(mToolbar, true); - } else { - AnimUtils.crossfade(mRecyclerViewSearch, null); - mSearchInput.getEditText().setText(mQuery); - setToolbarScrollingLock(mToolbar, false); - } - } - - @Override - public void onBackPressed() { - if (mRecyclerViewSearch.getVisibility() == View.VISIBLE) { - if (TextUtils.isEmpty(mQuery)) { - super.onBackPressed(); - } else { - closeHistory(); - } - } else { - super.onBackPressed(); - } - } - - private void closeHistory() { - LayoutUtils.hideSoftKeyboard(mRecyclerView); - if (!mRecyclerViewSearch.isFocused()) { - onFocusChange(mRecyclerViewSearch, false); - } - mRecyclerView.requestFocus(); - } - - @Override - public void onTextChanged(CharSequence text) { - mHistoryAdapter.requeryAsync(text.toString()); - } - - @Override - public void onChoiceChanged(ActionMode actionMode, ModalChoiceController controller, int count) { - actionMode.setTitle(String.valueOf(count)); - actionMode.getMenu().findItem(R.id.action_share).setVisible(count == 1); - } - - @Override - public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { - getMenuInflater().inflate(R.menu.actionmode_mangas, menu); - menu.findItem(R.id.action_save).setVisible(true); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { - final int[] indeces = mResultsAdapter.getChoiceController().getSelectedItemsPositions(); - final MangaInfo[] items = new MangaInfo[indeces.length]; - for (int i=0;i { - - @Nullable - private final ProviderSummary mSummary; - - - SearchTask(SearchActivity a) { - super(a); - if (a.mCurrentProvider == null) { - mSummary = a.mProviders.pop(); - a.mCurrentProvider = a.mProviderManager.instanceProvider(mSummary.aClass); - a.mPage = 0; - } else { - mSummary = null; - } - } - - @SuppressWarnings("ConstantConditions") - @Override - protected MangaList doInBackground(Void... params) { - try { - return getObject().mCurrentProvider.search( - getObject().mQuery, - getObject().mPage); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(@NonNull SearchActivity a, MangaList mangaInfos) { - a.mResultsAdapter.loadingComplete(); - if (mangaInfos != null && !mangaInfos.isEmpty()) { - a.mPage++; - a.mResultsAdapter.append(mSummary, mangaInfos); - if (a.mProgressBar.getVisibility() == View.VISIBLE) { - AnimUtils.crossfade(a.mProgressBar, null); - a.mResultsAdapter.setFooterProgress(); - } - a.mResultsAdapter.onScrolled(a.mRecyclerView); - } else { - //nothing found - if (a.mProviders.isEmpty()) { - //no more providers - if (a.mStage == 0) { - if (a.mResultsAdapter.hasItems()) { - a.mResultsAdapter.setFooterButton(a.getString(R.string.search_on_another_sources)); - } else { - a.onMoreButtonClick(); - } - } else { - if (a.mResultsAdapter.hasItems()) { - a.mResultsAdapter.hideFooter(); - } else { - AnimUtils.crossfade(a.mProgressBar, a.mTextViewHolder); - } - } - } else { - //next provider - a.mCurrentProvider = null; - if (a.mResultsAdapter.hasItems()) { - a.mResultsAdapter.setFooterProgress(); - a.mResultsAdapter.onScrolled(a.mRecyclerView); - } else { - a.onLoadMore(); - } - } - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/WelcomeActivity.java b/app/src/main/java/org/nv95/openmanga/activities/WelcomeActivity.java deleted file mode 100755 index 62a7b600..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/WelcomeActivity.java +++ /dev/null @@ -1,222 +0,0 @@ -package org.nv95.openmanga.activities; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v4.view.ViewCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.Toolbar; -import android.text.Html; -import android.view.View; -import android.widget.Button; -import android.widget.Checkable; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.BuildConfig; -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.settings.SettingsActivity2; -import org.nv95.openmanga.dialogs.DirSelectDialog; -import org.nv95.openmanga.dialogs.StorageSelectDialog; -import org.nv95.openmanga.providers.staff.Providers; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.BackupRestoreUtil; -import org.nv95.openmanga.utils.MangaStore; - -import java.io.File; - -/** - * Created by nv95 on 18.10.15. - */ -public class WelcomeActivity extends BaseAppActivity { - - public static final int REQUEST_ONBOARDING = 131; - public static final int REQUEST_SOURCES = 132; - - private static final int WELCOME_CHANGELOG = 1; - private static final int WELCOME_ONBOARDING = 2; - - private TextView mTextViewSources; - private TextView mTextViewTheme; - private TextView mTextViewStorage; - - /** - * - * @param context - * @return true if first call - */ - public static boolean show(Activity context) { - SharedPreferences prefs = context.getSharedPreferences(WelcomeActivity.class.getName(), MODE_PRIVATE); - int version = BuildConfig.VERSION_CODE; - int lastVersion = prefs.getInt("version", -1); - if (lastVersion == -1) { - context.startActivityForResult( - new Intent(context, WelcomeActivity.class) - .putExtra("mode", WELCOME_ONBOARDING), - REQUEST_ONBOARDING - ); - } else if (lastVersion < version) { - if (version % 2 == 0 && prefs.getBoolean("showChangelog", true)) { - context.startActivity( - new Intent(context, WelcomeActivity.class) - .putExtra("mode", WELCOME_CHANGELOG) - ); - } - } - prefs.edit().putInt("version", version).apply(); - return lastVersion == -1; - } - - @SuppressLint("SetTextI18n") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_welcome); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - //init view - mTextViewSources = (TextView) findViewById(R.id.textViewSources); - mTextViewStorage = (TextView) findViewById(R.id.textViewStorage); - mTextViewTheme = (TextView) findViewById(R.id.textViewTheme); - //--------- - int mode = getIntent().getIntExtra("mode", WELCOME_CHANGELOG); - switch (mode) { - case WELCOME_CHANGELOG: - findViewById(R.id.page_changelog).setVisibility(View.VISIBLE); - ((TextView) findViewById(R.id.textView)).setText( - Html.fromHtml(AppHelper.getRawString(this, R.raw.changelog)) - ); - findViewById(R.id.button_close).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); - findViewById(R.id.checkBox_showChangelog).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (v instanceof Checkable) { - getSharedPreferences(WelcomeActivity.class.getName(), MODE_PRIVATE) - .edit().putBoolean("showChangelog", ((Checkable) v).isChecked()).apply(); - } - } - }); - break; - case WELCOME_ONBOARDING: - ViewCompat.setElevation(toolbar, 0); - findViewById(R.id.page_onboarding1).setVisibility(View.VISIBLE); - findViewById(R.id.button_done).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - final String[] themes = getResources().getStringArray(R.array.themes_names); - int selTheme = Integer.parseInt(prefs.getString("theme", "0")); - mTextViewTheme.setText(getString(R.string.theme) + ": " + themes[selTheme]); - mTextViewTheme.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new AlertDialog.Builder(WelcomeActivity.this) - .setItems(themes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - mTextViewTheme.setText(getString(R.string.theme) + ": " + themes[i]); - prefs.edit() - .putString("theme", String.valueOf(i)) - .apply(); - dialogInterface.dismiss(); - } - }) - .create() - .show(); - } - }); - Button buttonRestore = (Button) findViewById(R.id.buttonRestore); - buttonRestore.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) { - BackupRestoreUtil.showRestoreDialog(WelcomeActivity.this); - } - } - }); - Button buttonSync = (Button) findViewById(R.id.buttonSync); - buttonSync.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - SettingsActivity2.openSyncSettings(WelcomeActivity.this, 0); - } - }); - int active = Math.min( - getSharedPreferences("providers", Context.MODE_PRIVATE).getInt("count", Providers.getCount()), - Providers.getCount() - ); - mTextViewSources.setText(getString(R.string.sources) + ": " + getString(R.string.providers_pref_summary, active, Providers.getCount())); - mTextViewSources.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - SettingsActivity2.openProvidersSettings(WelcomeActivity.this, REQUEST_SOURCES); - } - }); - mTextViewStorage.setText(MangaStore.getMangasDir(this).getPath()); - mTextViewStorage.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new StorageSelectDialog(WelcomeActivity.this, false) - .setDirSelectListener(new DirSelectDialog.OnDirSelectListener() { - @Override - public void onDirSelected(File dir) { - prefs.edit() - .putString("mangadir", dir.getPath()) - .apply(); - mTextViewStorage.setText(dir.getPath()); - } - }) - .show(); - } - }); - break; - default: - finish(); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case BackupRestoreUtil.BACKUP_IMPORT_CODE: - if (resultCode == RESULT_OK) { - File file = AppHelper.getFileFromUri(this, data.getData()); - if (file != null) { - new BackupRestoreUtil(this).restore(file); - } else { - Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show(); - } - } - break; - case REQUEST_SOURCES: - int active = Math.min( - getSharedPreferences("providers", Context.MODE_PRIVATE).getInt("count", Providers.getCount()), - Providers.getCount() - ); - mTextViewSources.setText(getString(R.string.sources) + ": " + getString(R.string.providers_pref_summary, active, Providers.getCount())); - break; - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - protected void onPermissionGranted(String permission) { - if (Manifest.permission.READ_EXTERNAL_STORAGE.equals(permission)) { - BackupRestoreUtil.showRestoreDialog(WelcomeActivity.this); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/AppearanceSettingsFragment.java deleted file mode 100644 index 2dad20e9..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/AppearanceSettingsFragment.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Activity; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.PreferencesUtils; - -/** - * Created by admin on 21.07.17. - */ - -public class AppearanceSettingsFragment extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_appearance); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - PreferencesUtils.bindPreferenceSummary(findPreference("theme"), (Preference.OnPreferenceChangeListener) getActivity()); - } - - @Override - public void onResume() { - super.onResume(); - Activity activity = getActivity(); - if (activity != null) { - activity.setTitle(R.string.appearance); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/GeneralSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/GeneralSettingsFragment.java deleted file mode 100644 index 28303064..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/GeneralSettingsFragment.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Activity; -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.widget.Toast; - -import org.nv95.openmanga.OpenMangaApplication; -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.PreferencesUtils; - -/** - * Created by admin on 24.07.17. - */ - -public class GeneralSettingsFragment extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_general); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Activity activity = getActivity(); - - PreferencesUtils.bindPreferenceSummary(findPreference("lang"), new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - OpenMangaApplication.setLanguage(preference.getContext().getApplicationContext().getResources(), (String) newValue); - int index = ((ListPreference) preference).findIndexOfValue((String) newValue); - String summ = ((ListPreference) preference).getEntries()[index].toString(); - preference.setSummary(summ); - Toast.makeText(preference.getContext(), R.string.need_restart, Toast.LENGTH_SHORT).show(); - return true; - } - }); - - findPreference("recommendations").setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - - PreferencesUtils.bindPreferenceSummary(findPreference("fav.categories")); - PreferencesUtils.bindPreferenceSummary(findPreference("defsection")); - } - - @Override - public void onResume() { - super.onResume(); - Activity activity = getActivity(); - if (activity != null) { - activity.setTitle(R.string.appearance); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/OtherSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/OtherSettingsFragment.java deleted file mode 100644 index eb4a66db..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/OtherSettingsFragment.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Activity; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceCategory; -import android.preference.PreferenceFragment; -import android.support.annotation.NonNull; -import android.widget.Toast; - -import org.nv95.openmanga.BuildConfig; -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.BaseAppActivity; -import org.nv95.openmanga.adapters.SearchHistoryAdapter; -import org.nv95.openmanga.helpers.ScheduleHelper; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.MangaStore; -import org.nv95.openmanga.utils.PreferencesUtils; -import org.nv95.openmanga.utils.StorageUtils; -import org.nv95.openmanga.utils.WeakAsyncTask; - -/** - * Created by admin on 21.07.17. - */ - -public class OtherSettingsFragment extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_other); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Activity activity = getActivity(); - findPreference("movemanga").setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - findPreference("backup").setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - findPreference("restore").setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - - Preference p = findPreference("mangadir"); - try { - p.setSummary(MangaStore.getMangasDir(activity).getPath()); - } catch (Exception e) { - p.setSummary(R.string.unknown); - } - p.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - - PreferencesUtils.bindPreferenceSummary(findPreference("cache_max"), new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - try { - int size = (int) newValue; - if (size >= ImageUtils.CACHE_MIN_MB && size <= ImageUtils.CACHE_MAX_MB) { - //noinspection ConstantConditions - int aval = StorageUtils.getFreeSpaceMb(preference.getContext().getExternalCacheDir().getPath()); - if (aval != 0 && size >= aval - 50) { - Toast.makeText(preference.getContext(), R.string.too_small_free_space, Toast.LENGTH_SHORT).show(); - return false; - } - return true; - } - } catch (Exception ignored) { - } - Toast.makeText(preference.getContext(), getString(R.string.cache_size_invalid, ImageUtils.CACHE_MIN_MB, ImageUtils.CACHE_MAX_MB), Toast.LENGTH_SHORT).show(); - return false; - } - }, activity.getString(R.string.size_mb)); - - - PreferencesUtils.bindPreferenceSummary(findPreference("save_threads")); - - findPreference("ccache").setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - - p = findPreference("csearchhist"); - p.setSummary(getString(R.string.items_, SearchHistoryAdapter.getHistorySize(activity))); - p.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - - findPreference("bugreport").setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - findPreference("use_tor").setOnPreferenceChangeListener((Preference.OnPreferenceChangeListener) activity); - - p = findPreference("update"); - if (BuildConfig.SELFUPDATE_ENABLED) { - p.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - long lastCheck = new ScheduleHelper(activity).getActionRawTime(ScheduleHelper.ACTION_CHECK_APP_UPDATES); - p.setSummary(getString(R.string.last_update_check, - lastCheck == -1 ? getString(R.string.unknown) : AppHelper.getReadableDateTimeRelative(lastCheck))); - } else if (p != null) { - PreferenceCategory cat = (PreferenceCategory) findPreference("cat_help"); - cat.removePreference(p); - cat.removePreference(findPreference("autoupdate")); - } - - p = findPreference("about"); - p.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); - p.setSummary(String.format(activity.getString(R.string.version), - BuildConfig.VERSION_NAME)); - - new CacheSizeTask(findPreference("ccache")).attach((BaseAppActivity) activity).start(); - } - - private static class CacheSizeTask extends WeakAsyncTask { - - CacheSizeTask(Preference object) { - super(object); - } - - @Override - protected void onPreExecute(@NonNull Preference preference) { - preference.setSummary(R.string.size_calculating); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected Float doInBackground(Void... voids) { - try { - return StorageUtils.dirSize(getObject().getContext().getExternalCacheDir()) / 1048576f; - } catch (Exception e) { - return null; - } - } - - @Override - protected void onPostExecute(@NonNull Preference preference, Float aFloat) { - preference.setSummary(String.format(preference.getContext().getString(R.string.cache_size), - aFloat == null ? 0 : aFloat)); - } - } - - @Override - public void onResume() { - super.onResume(); - Activity activity = getActivity(); - if (activity != null) { - activity.setTitle(R.string.other_settings); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/PreferenceHeader.java b/app/src/main/java/org/nv95/openmanga/activities/settings/PreferenceHeader.java deleted file mode 100644 index ae78f19f..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/PreferenceHeader.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.support.annotation.DrawableRes; -import android.support.annotation.StringRes; -import android.support.v4.content.ContextCompat; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * Created by admin on 24.07.17. - */ - -public class PreferenceHeader { - - public String title; - public Drawable icon; - - public PreferenceHeader(Context context, @StringRes int title, @DrawableRes int icon) { - this.title = context.getString(title); - this.icon = ContextCompat.getDrawable(context, icon); - this.icon.setColorFilter(LayoutUtils.getAttrColor(context, R.attr.colorAccent), PorterDuff.Mode.SRC_ATOP); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/ProviderPreferencesActivity.java b/app/src/main/java/org/nv95/openmanga/activities/settings/ProviderPreferencesActivity.java deleted file mode 100644 index eedbfa1e..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/ProviderPreferencesActivity.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.Toolbar; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.BaseAppActivity; -import org.nv95.openmanga.providers.staff.ProviderSummary; -import org.nv95.openmanga.providers.staff.Providers; - -import java.lang.reflect.Method; - -/** - * Created by nv95 on 21.11.16. - */ - -public class ProviderPreferencesActivity extends BaseAppActivity { - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_settings); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - enableHomeAsUp(); - int pid = getIntent().getIntExtra("provider", -1); - if (pid == -1) { - finish(); - return; - } - ProviderSummary ps = Providers.getById(pid); - if (ps == null) { - finish(); - return; - } - setTitle(ps.name); - setSubtitle(R.string.action_settings); - ProviderPrefFragment fragment = new ProviderPrefFragment(); - fragment.setArguments(getIntent().getExtras()); - getFragmentManager().beginTransaction() - .replace(R.id.content, fragment) - .commit(); - } - - public static class ProviderPrefFragment extends PreferenceFragment { - - private ProviderSummary mProvider; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mProvider = Providers.getById(getArguments().getInt("provider")); - getPreferenceManager().setSharedPreferencesName("prov_" + mProvider.aClass.getSimpleName()); - addPreferencesFromResource(mProvider.preferences); - - Preference testPref = findPreference("auth_test"); - if (testPref != null) { - testPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); - new TestAuthTask(getActivity()).executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR, - prefs.getString("login",""), - prefs.getString("password",""), - prefs.getString("domain","") - ); - return true; - } - }); - } - } - - private class TestAuthTask extends AsyncTask implements DialogInterface.OnCancelListener { - - private final Activity mActivity; - private final ProgressDialog mDialog; - - TestAuthTask(Activity activity) { - this.mActivity = activity; - mDialog = new ProgressDialog(mActivity); - mDialog.setOwnerActivity(mActivity); - mDialog.setMessage(mActivity.getString(R.string.wait)); - mDialog.setOnCancelListener(this); - mDialog.setCancelable(true); - mDialog.setCanceledOnTouchOutside(true); - mDialog.setIndeterminate(true); - } - - @Override - protected void onPreExecute() { - mDialog.show(); - super.onPreExecute(); - } - - @Override - protected Boolean doInBackground(String... strings) { - try { - Method m = mProvider.aClass.getMethod("auth", String.class, String.class, String.class); - return (Boolean) m.invoke(null, strings[0], strings[1], strings[2]); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(Boolean aBoolean) { - super.onPostExecute(aBoolean); - int msg = R.string.error; - if (Boolean.TRUE.equals(aBoolean)) { - msg = R.string.successfully; - } else if (Boolean.FALSE.equals(aBoolean)) { - msg = R.string.auth_failed; - } - mDialog.dismiss(); - new AlertDialog.Builder(mActivity) - .setMessage(msg) - .setPositiveButton(R.string.close, null) - .create() - .show(); - } - - @Override - public void onCancel(DialogInterface dialogInterface) { - this.cancel(false); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/ProviderSelectFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/ProviderSelectFragment.java deleted file mode 100644 index b5658d5f..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/ProviderSelectFragment.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Activity; -import android.app.Fragment; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.ProvidersAdapter; -import org.nv95.openmanga.components.DividerItemDecoration; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.providers.staff.ProviderSummary; - -import java.util.Collections; -import java.util.List; - -/** - * Created by admin on 23.07.17. - */ - -public class ProviderSelectFragment extends Fragment implements ProvidersAdapter.OnStartDragListener { - - private RecyclerView mRecyclerView; - private List mProviders; - private MangaProviderManager mProviderManager; - private ProvidersAdapter mAdapter; - private ItemTouchHelper mItemTouchHelper; - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - View contentView = inflater.inflate(R.layout.fragment_provselect, container, false); - mRecyclerView = contentView.findViewById(R.id.recyclerView); - return contentView; - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Activity activity = getActivity(); - mProviderManager = new MangaProviderManager(activity); - mProviders = mProviderManager.getOrderedProviders(); - - mRecyclerView.setLayoutManager(new LinearLayoutManager(activity)); - mRecyclerView.setAdapter(mAdapter = new ProvidersAdapter(activity, mProviders, this)); - mAdapter.setActiveCount(mProviderManager.getProvidersCount()); - mRecyclerView.addItemDecoration(new DividerItemDecoration(activity)); - - mItemTouchHelper = new ItemTouchHelper(new OrderManager()); - mItemTouchHelper.attachToRecyclerView(mRecyclerView); - } - - @Override - public void onStartDrag(RecyclerView.ViewHolder viewHolder) { - mItemTouchHelper.startDrag(viewHolder); - } - - private class OrderManager extends ItemTouchHelper.Callback { - - @Override - public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - int fromPosition = viewHolder.getAdapterPosition(); - int toPosition = target.getAdapterPosition(); - - if (toPosition == mProviders.size() + 1 || toPosition == mAdapter.getActiveCount()) { //drop to footer/divider - return false; - } - - if (viewHolder instanceof ProvidersAdapter.DividerHolder) { //divider - if (toPosition == 0) { - return false; //enabled providers count must be > 0 - } - mAdapter.setActiveCount(toPosition); - mAdapter.notifyItemMoved(fromPosition, toPosition); - mProviderManager.setProvidersCount(toPosition); - return true; - } - - if (fromPosition > mAdapter.getActiveCount() && toPosition < mAdapter.getActiveCount()) { - return false; - } - - if (fromPosition < mAdapter.getActiveCount() && toPosition > mAdapter.getActiveCount()) { - return false; - } - - if (fromPosition > mAdapter.getActiveCount()) { - fromPosition--; - } - - if (toPosition > mAdapter.getActiveCount()) { - toPosition--; - } - - if (fromPosition < toPosition) { - for (int i = fromPosition; i < toPosition; i++) { - Collections.swap(mProviders, i, i + 1); - } - } else { - for (int i = fromPosition; i > toPosition; i--) { - Collections.swap(mProviders, i, i - 1); - } - } - - mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); - mProviderManager.updateOrder(mProviders); - return true; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - - } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - } - - @Override - public void onResume() { - super.onResume(); - Activity activity = getActivity(); - if (activity != null) { - activity.setTitle(R.string.manga_catalogues); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/ReadSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/ReadSettingsFragment.java deleted file mode 100644 index 433d0f9a..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/ReadSettingsFragment.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Activity; -import android.os.Bundle; -import android.preference.PreferenceFragment; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.PreferencesUtils; - -/** - * Created by admin on 21.07.17. - */ - -public class ReadSettingsFragment extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_read); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Activity activity = getActivity(); - PreferencesUtils.bindPreferenceSummary(findPreference("direction")); - PreferencesUtils.bindPreferenceSummary(findPreference("r2_mode")); - PreferencesUtils.bindPreferenceSummary(findPreference("scalemode")); - PreferencesUtils.bindPreferenceSummary(findPreference("preload")); - } - - @Override - public void onResume() { - super.onResume(); - Activity activity = getActivity(); - if (activity != null) { - activity.setTitle(R.string.action_reading_options); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/SettingsActivity2.java b/app/src/main/java/org/nv95/openmanga/activities/settings/SettingsActivity2.java deleted file mode 100644 index a0c9eb01..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/SettingsActivity2.java +++ /dev/null @@ -1,616 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.Manifest; -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.preference.Preference; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.AppBarLayout; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.FrameLayout; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.AboutActivity; -import org.nv95.openmanga.activities.BaseAppActivity; -import org.nv95.openmanga.adapters.SearchHistoryAdapter; -import org.nv95.openmanga.dialogs.DirSelectDialog; -import org.nv95.openmanga.dialogs.LocalMoveDialog; -import org.nv95.openmanga.dialogs.RecommendationsPrefDialog; -import org.nv95.openmanga.dialogs.StorageSelectDialog; -import org.nv95.openmanga.helpers.DirRemoveHelper; -import org.nv95.openmanga.helpers.ScheduleHelper; -import org.nv95.openmanga.helpers.SyncHelper; -import org.nv95.openmanga.items.RESTResponse; -import org.nv95.openmanga.providers.AppUpdatesProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.services.SyncService; -import org.nv95.openmanga.services.UpdateService; -import org.nv95.openmanga.utils.AnimUtils; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.BackupRestoreUtil; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.ProgressAsyncTask; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.io.File; -import java.util.ArrayList; - -import info.guardianproject.netcipher.proxy.OrbotHelper; - -/** - * Created by admin on 24.07.17. - */ - -public class SettingsActivity2 extends BaseAppActivity implements AdapterView.OnItemClickListener, - Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener, FragmentManager.OnBackStackChangedListener { - - private static final int SECTION_READER = 2; - private static final int SECTION_PROVIDERS = 3; - private static final int SECTION_SYNC = 4; - private static final int SECTION_CHUPD = 5; - - private Fragment mFragment; - private SettingsHeadersAdapter mAdapter; - private ArrayList mHeaders; - private AppBarLayout mAppBarLayout; - private CardView mCardView; - private FrameLayout mContent; - private TextView mTitleTextView; - private boolean mIsTwoPanesMode; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_settings2); - setSupportActionBar(R.id.toolbar); - enableHomeAsUp(); - disableTitle(); - - mIsTwoPanesMode = LayoutUtils.isTabletLandscape(this); - - mContent = (FrameLayout) findViewById(R.id.content); - mCardView = (CardView) findViewById(R.id.cardView); - mTitleTextView = (TextView) findViewById(R.id.textView_title); - mAppBarLayout = (AppBarLayout) findViewById(R.id.appbar_container); - RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - mHeaders = new ArrayList<>(); - - mHeaders.add(new PreferenceHeader(this, R.string.general, R.drawable.ic_pref_home)); - mHeaders.add(new PreferenceHeader(this, R.string.appearance, R.drawable.ic_pref_appearance)); - mHeaders.add(new PreferenceHeader(this, R.string.manga_catalogues, R.drawable.ic_pref_sources)); - mHeaders.add(new PreferenceHeader(this, R.string.action_reading_options, R.drawable.ic_pref_reader)); - mHeaders.add(new PreferenceHeader(this, R.string.checking_new_chapters, R.drawable.ic_pref_cheknew)); - mHeaders.add(new PreferenceHeader(this, R.string.sync, R.drawable.ic_pref_sync)); - mHeaders.add(new PreferenceHeader(this, R.string.more_, R.drawable.ic_pref_more)); - - recyclerView.setAdapter(mAdapter = new SettingsHeadersAdapter(mHeaders, this)); - getFragmentManager().addOnBackStackChangedListener(this); - - mFragment = null; - int section = getIntent().getIntExtra("section", 0); - switch (section) { - case SECTION_READER: - mFragment = new ReadSettingsFragment(); - if (mIsTwoPanesMode) { - mAdapter.setActivatedPosition(3); - } - break; - case SECTION_PROVIDERS: - mFragment = new ProviderSelectFragment(); - if (mIsTwoPanesMode) { - mAdapter.setActivatedPosition(2); - } - break; - case SECTION_SYNC: - mFragment = SyncHelper.get(this).isAuthorized() ? new SyncSettingsFragment() : new SyncLoginFragment(); - if (mIsTwoPanesMode) { - mAdapter.setActivatedPosition(5); - } - break; - case SECTION_CHUPD: - mFragment = new UpdatesCheckSettingsFragment(); - if (mIsTwoPanesMode) { - mAdapter.setActivatedPosition(4); - } - break; - default: - if (mIsTwoPanesMode) { - mFragment = new GeneralSettingsFragment(); - if (mIsTwoPanesMode) { - mAdapter.setActivatedPosition(0); - } - } else { - mFragment = null; - } - } - - if (mFragment != null) { - if (!mIsTwoPanesMode) { - mAppBarLayout.setExpanded(false, false); - AnimUtils.noanim(mCardView, mContent); - } - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.replace(R.id.content, mFragment); - transaction.commit(); - } - } - - @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - switch (i) { - case 0: - openFragment(new GeneralSettingsFragment()); - break; - case 1: - openFragment(new AppearanceSettingsFragment()); - break; - case 2: - openFragment(new ProviderSelectFragment()); - break; - case 3: - openFragment(new ReadSettingsFragment()); - break; - case 4: - openFragment(new UpdatesCheckSettingsFragment()); - break; - case 5: - if (SyncHelper.get(this).isAuthorized()) { - openFragment(new SyncSettingsFragment()); - } else { - openFragment(new SyncLoginFragment()); - } - break; - case 6: - openFragment(new OtherSettingsFragment()); - break; - } - if (mIsTwoPanesMode) { - mAdapter.setActivatedPosition(i); - } - } - - public void openFragment(Fragment fragment) { - mFragment = fragment; - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.replace(R.id.content, mFragment); - if (!mIsTwoPanesMode) { - transaction.addToBackStack(null); - } - transaction.commit(); - } - - @Override - public void setTitle(int titleId) { - if (!mIsTwoPanesMode) { - mTitleTextView.setText(titleId); - } - } - - @Override - public void setTitle(CharSequence title) { - if (!mIsTwoPanesMode) { - mTitleTextView.setText(title); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - if (!getFragmentManager().popBackStackImmediate()) { - finish(); - } - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - @Override - public boolean onPreferenceClick(final Preference preference) { - switch (preference.getKey()) { - case "bugreport": - FileLogger.sendLog(this); - return true; - case "csearchhist": - SearchHistoryAdapter.clearHistory(this); - Toast.makeText(this, R.string.completed, Toast.LENGTH_SHORT).show(); - preference.setSummary(getString(R.string.items_, 0)); - return true; - case "about": - startActivity(new Intent(this, AboutActivity.class)); - return true; - case "recommendations": - new RecommendationsPrefDialog(this, null).show(); - return true; - case "backup": - if (checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - BackupRestoreUtil.showBackupDialog(this); - } - return true; - case "restore": - if (checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) { - BackupRestoreUtil.showRestoreDialog(this); - } - return true; - case "ccache": - new CacheClearTask(preference).attach(this).start(); - return true; - case "movemanga": - if (checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) && checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - new LocalMoveDialog(this, - LocalMangaProvider.getInstance(this).getAllIds()) - .showSelectSource(null); - } - return true; - case "mangadir": - if (!checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) { - return true; - } - new StorageSelectDialog(this) - .setDirSelectListener(new DirSelectDialog.OnDirSelectListener() { - @Override - public void onDirSelected(final File dir) { - if (!dir.canWrite()) { - Toast.makeText(SettingsActivity2.this, R.string.dir_no_access, - Toast.LENGTH_SHORT).show(); - return; - } - preference.setSummary(dir.getPath()); - preference.getEditor() - .putString("mangadir", dir.getPath()).apply(); - checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE); - } - }) - .show(); - return true; - case "update": - new CheckUpdatesTask(this).attach(this).start(); - return true; - case "sync.start": - SyncService.start(this); - return true; - case "sync.username": - new AlertDialog.Builder(this) - .setMessage(R.string.logout_confirm) - .setPositiveButton(R.string.logout, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - new SyncLogoutTask(SettingsActivity2.this).start(); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create().show(); - return true; - default: - try { - if (preference.getKey().startsWith("sync.dev")) { - int devId = Integer.parseInt(preference.getKey().substring(9)); - detachDevice(devId, preference); - } - } catch (Exception e) { - e.printStackTrace(); - } - return false; - } - } - - public void onSaveInstanceState(Bundle outState){ - if (mFragment != null) { - outState.putString("fragment", mFragment.getClass().getName()); - } - } - public void onRestoreInstanceState(Bundle inState){ - String fragment = inState.getString("fragment"); - if (fragment != null) { - try { - Fragment f = (Fragment) Class.forName(fragment).newInstance(); - openFragment(f); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - @Override - public boolean onPreferenceChange(Preference preference, Object o) { - switch (preference.getKey()) { - case "use_tor": - if (Boolean.TRUE.equals(o)) { - if (NetworkUtils.setUseTor(this, true)) { - return true; - } else { - new AlertDialog.Builder(this) - .setTitle(R.string.use_tor_proxy) - .setMessage(R.string.orbot_required) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialogInterface, int i) { - OrbotHelper.get(SettingsActivity2.this).installOrbot(SettingsActivity2.this); - } - }).create().show(); - return false; - } - } else if (Boolean.FALSE.equals(o)) { - NetworkUtils.setUseTor(this, false); - return true; - } - break; - case "theme": - mCardView.postDelayed(new Runnable() { - @Override - public void run() { - recreate(); - } - }, 100); - return true; - } - return false; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case BackupRestoreUtil.BACKUP_IMPORT_CODE: - if (resultCode == RESULT_OK) { - File file = AppHelper.getFileFromUri(this, data.getData()); - if (file != null) { - new BackupRestoreUtil(this).restore(file); - } else { - Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show(); - } - } - break; - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void onBackPressed() { - if (!getFragmentManager().popBackStackImmediate()) { - super.onBackPressed(); - } - } - - @Override - public void onBackStackChanged() { - if (getFragmentManager().getBackStackEntryCount() == 0) { - mFragment = null; - AnimUtils.crossfade(mContent, mCardView); - setTitle(R.string.action_settings); - mAppBarLayout.setExpanded(true, true); - } else { - AnimUtils.crossfade(mCardView, mContent); - mAppBarLayout.setExpanded(false, true); - } - } - - private static class CacheClearTask extends WeakAsyncTask { - - CacheClearTask(Preference object) { - super(object); - } - - @Override - protected void onPreExecute(@NonNull Preference preference) { - preference.setSummary(R.string.cache_clearing); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected Void doInBackground(Void... params) { - try { - File dir = getObject().getContext().getExternalCacheDir(); - new DirRemoveHelper(dir).run(); - } catch (Exception ignored) { - } - return null; - } - - @Override - protected void onPostExecute(@NonNull Preference preference, Void aVoid) { - preference.setSummary(String.format(preference.getContext().getString(R.string.cache_size), 0f)); - } - } - - private static class CheckUpdatesTask extends WeakAsyncTask implements DialogInterface.OnCancelListener { - - private int mSelected = 0; - private final ProgressDialog mDialog; - - CheckUpdatesTask(SettingsActivity2 activity) { - super(activity); - mDialog = new ProgressDialog(activity); - mDialog.setMessage(activity.getString(R.string.checking_updates)); - mDialog.setCancelable(true); - mDialog.setIndeterminate(true); - mDialog.setOnCancelListener(this); - } - - @Override - protected void onPreExecute(@NonNull SettingsActivity2 activity) { - mDialog.show(); - } - - @Override - protected AppUpdatesProvider doInBackground(Void... params) { - return new AppUpdatesProvider(); - } - - @Override - protected void onCancelled() { - super.onCancelled(); - mDialog.dismiss(); - } - - @Override - protected void onPostExecute(@NonNull final SettingsActivity2 activity, AppUpdatesProvider appUpdatesProvider) { - mDialog.dismiss(); - if (appUpdatesProvider.isSuccess()) { - if (activity.mFragment instanceof OtherSettingsFragment) { - Preference p = ((OtherSettingsFragment) activity.mFragment).findPreference("update"); - if (p != null) { - p.setSummary(activity.getString(R.string.last_update_check, - AppHelper.getReadableDateTimeRelative(System.currentTimeMillis()))); - } - } - new ScheduleHelper(activity).actionDone(ScheduleHelper.ACTION_CHECK_APP_UPDATES); - final AppUpdatesProvider.AppUpdateInfo[] updates = appUpdatesProvider.getLatestUpdates(); - if (updates.length == 0) { - new AlertDialog.Builder(activity) - .setMessage(R.string.no_app_updates) - .setPositiveButton(R.string.close, null) - .create().show(); - return; - } - final String[] titles = new String[updates.length]; - for (int i = 0; i < titles.length; i++) { - titles[i] = updates[i].getVersionName(); - } - new AlertDialog.Builder(activity) - .setTitle(R.string.update) - .setSingleChoiceItems(titles, 0, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mSelected = which; - } - }) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - UpdateService.start(activity, updates[mSelected].getUrl()); - } - }) - .setCancelable(true) - .create().show(); - } else { - Toast.makeText(activity, R.string.error, Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onCancel(DialogInterface dialogInterface) { - this.cancel(false); - } - } - - private void detachDevice(final int devId, final Preference p) { - new AlertDialog.Builder(this) - .setMessage(getString(R.string.device_detach_confirm, p.getTitle().toString())) - .setPositiveButton(R.string.detach, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - p.setSelectable(false); - new DeviceDetachTask(p).attach(SettingsActivity2.this).start(devId); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create().show(); - } - - private static class DeviceDetachTask extends WeakAsyncTask { - - DeviceDetachTask(Preference preference) { - super(preference); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected RESTResponse doInBackground(Integer... integers) { - try { - return SyncHelper.get(getObject().getContext()).detachDevice(integers[0]); - } catch (Exception e) { - e.printStackTrace(); - return RESTResponse.fromThrowable(e); - } - } - - @Override - protected void onPostExecute(@NonNull Preference p, RESTResponse restResponse) { - if (restResponse.isSuccess()) { - p.setEnabled(false); - p.setSummary(R.string.device_detached); - Toast.makeText(p.getContext(), R.string.device_detached, Toast.LENGTH_SHORT).show(); - } else { - p.setSelectable(true); - Toast.makeText(p.getContext(), restResponse.getMessage(), Toast.LENGTH_SHORT).show(); - } - } - } - - private static class SyncLogoutTask extends ProgressAsyncTask { - - SyncLogoutTask(BaseAppActivity activity) { - super(activity); - setCancelable(false); - } - - @Override - protected RESTResponse doInBackground(Void... voids) { - try { - return SyncHelper.get(getActivity()).logout(); - } catch (Exception e) { - e.printStackTrace(); - return RESTResponse.fromThrowable(e); - } - } - - @Override - protected void onPostExecute(@NonNull BaseAppActivity activity, RESTResponse restResponse) { - if (restResponse.isSuccess()) { - ((SettingsActivity2)activity).openFragment(new SyncLoginFragment()); - } else { - Toast.makeText(activity, restResponse.getMessage(), Toast.LENGTH_SHORT).show(); - } - } - } - - private static void openSettings(Context context, int requestCode, int section) { - Intent intent = new Intent(context, SettingsActivity2.class); - intent.putExtra("section", section); - if (requestCode != 0) { - if (context instanceof Activity) { - ((Activity) context).startActivityForResult(intent, requestCode); - return; - } - } - context.startActivity(intent); - } - - public static void openReaderSettings(Context context, int requestCode) { - openSettings(context, requestCode, SECTION_READER); - } - - public static void openProvidersSettings(Context context, int requestCode) { - openSettings(context, requestCode, SECTION_PROVIDERS); - } - - public static void openSyncSettings(Context context, int requestCode) { - openSettings(context, requestCode, SECTION_SYNC); - } - - - public static void openChaptersCheckSettings(Context context, int requestCode) { - openSettings(context, requestCode, SECTION_CHUPD); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/SettingsHeadersAdapter.java b/app/src/main/java/org/nv95/openmanga/activities/settings/SettingsHeadersAdapter.java deleted file mode 100644 index c528f674..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/SettingsHeadersAdapter.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.support.v4.content.ContextCompat; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; - -import java.util.ArrayList; - -/** - * Created by admin on 24.07.17. - */ - -public class SettingsHeadersAdapter extends RecyclerView.Adapter { - - private final ArrayList mDataset; - private final AdapterView.OnItemClickListener mClickListener; - private int mCurrentPosition = -1; - - public SettingsHeadersAdapter(ArrayList headers, AdapterView.OnItemClickListener clickListener) { - mDataset = headers; - mClickListener = clickListener; - setHasStableIds(true); - } - - @Override - public PreferenceHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new PreferenceHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_pref_header, parent,false), mClickListener); - } - - public void setActivatedPosition(int pos) { - int lastPos = mCurrentPosition; - mCurrentPosition = pos; - if (lastPos != -1) { - notifyItemChanged(lastPos); - } - if (mCurrentPosition != -1) { - notifyItemChanged(mCurrentPosition); - } - } - - @Override - public void onBindViewHolder(PreferenceHolder holder, int position) { - PreferenceHeader item = mDataset.get(position); - holder.text.setText(item.title); - holder.icon.setImageDrawable(item.icon); - holder.setActivated(position == mCurrentPosition); - } - - @Override - public long getItemId(int position) { - return mDataset.get(position).title.hashCode(); - } - - @Override - public int getItemCount() { - return mDataset.size(); - } - - static class PreferenceHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - - private final ImageView icon; - private final TextView text; - private final AdapterView.OnItemClickListener clickListener; - - PreferenceHolder(View itemView, AdapterView.OnItemClickListener listener) { - super(itemView); - icon = itemView.findViewById(android.R.id.icon); - text = itemView.findViewById(android.R.id.text1); - clickListener = listener; - itemView.setOnClickListener(this); - } - - @Override - public void onClick(View view) { - clickListener.onItemClick(null, itemView, getAdapterPosition(), getItemId()); - } - - void setActivated(boolean activated) { - if (activated) { - itemView.setBackgroundColor(ContextCompat.getColor(itemView.getContext(), R.color.light_gray)); - } else { - ViewCompat.setBackground(itemView, LayoutUtils.getSelectableBackground(itemView.getContext())); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/SyncLoginFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/SyncLoginFragment.java deleted file mode 100644 index 67c90d3a..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/SyncLoginFragment.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Fragment; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.BaseAppActivity; -import org.nv95.openmanga.helpers.SyncHelper; -import org.nv95.openmanga.items.RESTResponse; -import org.nv95.openmanga.services.SyncService; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.ProgressAsyncTask; - -/** - * Created by admin on 24.07.17. - */ - -public class SyncLoginFragment extends Fragment implements View.OnClickListener { - - private EditText mEditLogin; - private EditText mEditPassword; - private Button mButtonLogin; - private Button mButtonRegister; - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_syncauth, container, false); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mEditLogin = view.findViewById(R.id.editLogin); - mEditPassword = view.findViewById(R.id.editPassword); - mButtonLogin = view.findViewById(R.id.buttonLogin); - mButtonRegister = view.findViewById(R.id.buttonRegister); - mButtonRegister.setOnClickListener(this); - mButtonLogin.setOnClickListener(this); - } - - @Override - public void onClick(View view) { - final String login = mEditLogin.getText().toString().trim(); - final String password = mEditPassword.getText().toString().trim(); - if (login.isEmpty()) { - LayoutUtils.showSoftKeyboard(mEditLogin); - return; - } - if (password.isEmpty()) { - LayoutUtils.showSoftKeyboard(mEditPassword); - return; - } - LayoutUtils.hideSoftKeyboard(mEditPassword); - new AuthTask((SettingsActivity2) getActivity(), view.getId() == R.id.buttonRegister) - .executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR, - login, - password - ); - } - - - private static class AuthTask extends ProgressAsyncTask implements DialogInterface.OnCancelListener { - - - private final boolean mRegister; - - AuthTask(SettingsActivity2 activity, boolean isRegister) { - super(activity); - mRegister = isRegister; - } - - - @Override - protected RESTResponse doInBackground(String... strings) { - try { - SyncHelper syncHelper = SyncHelper.get(getActivity()); - if (mRegister) { - return syncHelper.register(strings[0], strings[1]); - } else { - return syncHelper.authorize(strings[0], strings[1]); - } - } catch (Exception e) { - e.printStackTrace(); - return RESTResponse.fromThrowable(e); - } - } - - @Override - protected void onPostExecute(@NonNull BaseAppActivity activity, RESTResponse restResponse) { - if (restResponse.isSuccess()) { - Toast.makeText(activity, R.string.successfully, Toast.LENGTH_SHORT).show(); - ((SettingsActivity2)activity).openFragment(new SyncSettingsFragment()); - SyncService.start(activity); - } else { - new AlertDialog.Builder(activity) - .setTitle(R.string.auth_failed) - .setMessage(restResponse.getMessage()) - .setPositiveButton(android.R.string.ok, null) - .create().show(); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/SyncSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/SyncSettingsFragment.java deleted file mode 100644 index da17aacc..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/SyncSettingsFragment.java +++ /dev/null @@ -1,182 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceCategory; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; -import android.view.View; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.BaseAppActivity; -import org.nv95.openmanga.helpers.SyncHelper; -import org.nv95.openmanga.items.SyncDevice; -import org.nv95.openmanga.services.SyncService; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.PreferencesUtils; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.util.ArrayList; - -/** - * Created by admin on 24.07.17. - */ - -public class SyncSettingsFragment extends PreferenceFragment { - - private final BroadcastReceiver mEventReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - int what = intent.getIntExtra("what", -1); - Preference p; - switch (what) { - case SyncService.MSG_UNAUTHORIZED: - Activity activity = getActivity(); - if (activity != null && activity instanceof SettingsActivity2) { - Toast.makeText(activity, R.string.auth_failed, Toast.LENGTH_SHORT).show(); - ((SettingsActivity2) activity).openFragment(new SyncLoginFragment()); - } - break; - case SyncService.MSG_HIST_STARTED: - p = findPreference("sync.history"); - p.setSummary(R.string.sync_started); - p.setEnabled(false); - break; - case SyncService.MSG_HIST_FAILED: - p = findPreference("sync.history"); - p.setSummary(R.string.sync_failed); - p.setEnabled(true); - break; - case SyncService.MSG_HIST_FINISHED: - p = findPreference("sync.history"); - p.setSummary(R.string.sync_finished); - p.setEnabled(true); - break; - case SyncService.MSG_FAV_STARTED: - p = findPreference("sync.favourites"); - p.setSummary(R.string.sync_started); - p.setEnabled(false); - break; - case SyncService.MSG_FAV_FAILED: - p = findPreference("sync.favourites"); - p.setSummary(R.string.sync_failed); - p.setEnabled(true); - break; - case SyncService.MSG_FAV_FINISHED: - p = findPreference("sync.favourites"); - p.setSummary(R.string.sync_finished); - p.setEnabled(true); - break; - } - } - }; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_sync); - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Context context = getActivity(); - SyncHelper syncHelper = SyncHelper.get(context); - Preference p = findPreference("sync.start"); - p.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) context); - - p = findPreference("sync.history"); - long lastSync = syncHelper.getLastHistorySync(); - p.setSummary(context.getString(R.string.last_sync, lastSync == 0 ? context.getString(R.string.unknown) : AppHelper.getReadableDateTimeRelative(lastSync))); - - p = findPreference("sync.favourites"); - lastSync = syncHelper.getLastFavouritesSync(); - p.setSummary(context.getString(R.string.last_sync, lastSync == 0 ? context.getString(R.string.unknown) : AppHelper.getReadableDateTimeRelative(lastSync))); - - PreferencesUtils.bindPreferenceSummary(findPreference("sync.interval")); - - p = findPreference("sync.username"); - PreferencesUtils.bindPreferenceSummary(p); - p.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) context); - - if (NetworkUtils.checkConnection(context)) { - new LoadDevicesTask(this) - .attach((BaseAppActivity) context) - .start(); - } - } - - @Override - public void onStart() { - super.onStart(); - Activity activity = getActivity(); - activity.registerReceiver(mEventReceiver, new IntentFilter(SyncService.SYNC_EVENT)); - } - - @Override - public void onStop() { - Activity activity = getActivity(); - activity.unregisterReceiver(mEventReceiver); - super.onStop(); - } - - private static class LoadDevicesTask extends WeakAsyncTask> { - - - LoadDevicesTask(SyncSettingsFragment syncSettingsFragment) { - super(syncSettingsFragment); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected ArrayList doInBackground(Void... voids) { - try { - return SyncHelper.get(getObject().getActivity()) - .getUserDevices(false); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(@NonNull SyncSettingsFragment f, ArrayList devices) { - if (devices == null) { - View v = f.getView(); - if (v != null) { - Snackbar.make(v, R.string.server_inaccessible, Snackbar.LENGTH_INDEFINITE).show(); - } else { - Toast.makeText(f.getActivity(), R.string.server_inaccessible, Toast.LENGTH_LONG).show(); - } - } else { - Context c = f.getActivity(); - PreferenceScreen ps = f.getPreferenceScreen(); - PreferenceCategory cat = new PreferenceCategory(c); - cat.setTitle(c.getString(R.string.sync_devices, devices.size())); - ps.addPreference(cat); - for (SyncDevice o : devices) { - Preference p = new Preference(c); - p.setTitle(o.name); - p.setSummary(AppHelper.getReadableDateTime(c, o.created_at)); - p.setKey("sync.dev." + o.id); - if (c instanceof Preference.OnPreferenceClickListener) { - p.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) c); - } else { - p.setSelectable(false); - } - cat.addPreference(p); - } - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/activities/settings/UpdatesCheckSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/activities/settings/UpdatesCheckSettingsFragment.java deleted file mode 100644 index ea9558cb..00000000 --- a/app/src/main/java/org/nv95/openmanga/activities/settings/UpdatesCheckSettingsFragment.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.nv95.openmanga.activities.settings; - -import android.os.Bundle; -import android.preference.PreferenceFragment; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.PreferencesUtils; - -/** - * Created by admin on 25.07.17. - */ - -public class UpdatesCheckSettingsFragment extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_chupd); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - PreferencesUtils.bindPreferenceSummary(findPreference("chupd.interval")); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/BookmarksAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/BookmarksAdapter.java deleted file mode 100644 index e09328ca..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/BookmarksAdapter.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.annotation.SuppressLint; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.Bookmark; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.ImageUtils; - -import java.util.List; - -/** - * Created by unravel22 on 18.02.17. - */ - -public class BookmarksAdapter extends RecyclerView.Adapter implements View.OnClickListener { - - private final List mBookmarks; - @Nullable - private final OnBookmarkClickListener mClickListener; - - public BookmarksAdapter(List list, @Nullable OnBookmarkClickListener clickListener) { - mBookmarks = list; - mClickListener = clickListener; - } - - @Override - public BookmarkHolder onCreateViewHolder(ViewGroup parent, int viewType) { - BookmarkHolder holder = new BookmarkHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bookmark, parent, false)); - holder.itemView.setOnClickListener(this); - return holder; - } - - @SuppressLint("SetTextI18n") - @Override - public void onBindViewHolder(BookmarkHolder holder, int position) { - Bookmark o = mBookmarks.get(position); - ImageUtils.setThumbnail(holder.imageView, "file://" + o.thumbnailFile); - holder.textView1.setText(o.name + "\n" + holder.imageView.getContext().getString(R.string.bookmark_pos, o.chapter, o.page)); - holder.textView2.setText(AppHelper.getReadableDateTimeRelative(o.datetime)); - holder.itemView.setTag(o); - } - - @Override - public int getItemCount() { - return mBookmarks.size(); - } - - @Override - public void onClick(View v) { - Object tag = v.getTag(); - if (mClickListener != null && tag instanceof Bookmark) { - mClickListener.onBookmarkSelected((Bookmark) tag); - } - } - - static class BookmarkHolder extends RecyclerView.ViewHolder { - - final ImageView imageView; - final TextView textView1; - final TextView textView2; - - BookmarkHolder(View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.imageView); - textView1 = itemView.findViewById(R.id.textView_title); - textView2 = itemView.findViewById(R.id.textView_subtitle); - } - } - - public interface OnBookmarkClickListener { - void onBookmarkSelected(Bookmark bookmark); - } -} - diff --git a/app/src/main/java/org/nv95/openmanga/adapters/ChaptersAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/ChaptersAdapter.java deleted file mode 100644 index 07df2272..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/ChaptersAdapter.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.content.Context; -import android.graphics.Typeface; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.lists.ChaptersList; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.LayoutUtils; - -import java.util.Collections; -import java.util.List; - -/** - * Created by unravel22 on 18.02.17. - */ - -public class ChaptersAdapter extends RecyclerView.Adapter implements OnChapterClickListener { - - private final int ITEM_DEFAULT = 0; - private final int ITEM_HEADER = 1; - - private final ChaptersList mDataset; - private int mLastNumber = 2; - private long mLastTime = 0; - @Nullable - private OnChapterClickListener mClickListener; - private final int[] mColors; - private boolean mReversed = false; - - public ChaptersAdapter(Context context) { - mClickListener = null; - mLastNumber = -1; - mDataset = new ChaptersList(); - mColors = new int[] { - LayoutUtils.getAttrColor(context, android.R.attr.textColorPrimary), - LayoutUtils.getAttrColor(context, android.R.attr.textColorSecondary) - }; - setHasStableIds(true); - } - - public void setOnItemClickListener(@Nullable OnChapterClickListener listener) { - mClickListener = listener; - } - - public void setExtra(@Nullable HistoryProvider.HistorySummary hs) { - mLastNumber = hs != null ? hs.getChapter() : -1; - mLastTime = hs != null ? hs.getTime() : 0; - } - - public void setData(List chapters) { - mDataset.clear(); - mDataset.addAll(chapters); - } - - public void reverse() { - Collections.reverse(mDataset); - notifyDataSetChanged(); - mReversed = !mReversed; - } - - public boolean isReversed() { - return mReversed; - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == ITEM_HEADER) { - return new HeaderHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.header_chapter, parent, false), this); - } else { - return new ChapterHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chapter, parent, false), this); - } - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if (holder instanceof ChapterHolder) { - MangaChapter ch = mDataset.get(position - (mLastNumber >= 0 ? 1 : 0)); - TextView tv = ((ChapterHolder) holder).getTextView(); - tv.setText(ch.name); - tv.setTextColor(ch.number >= mLastNumber ? mColors[0] : mColors[1]); - tv.setTypeface(ch.number == mLastNumber ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT); - } else if (holder instanceof HeaderHolder) { - MangaChapter ch = mDataset.getByNumber(mLastNumber); - if (ch != null) { - ((HeaderHolder) holder).textViewTitle.setText(ch.name); - ((HeaderHolder) holder).textViewSubtitle.setText(AppHelper.getReadableDateTimeRelative(mLastTime)); - } - } - } - - @Override - public int getItemViewType(int position) { - return (position == 0 && mLastNumber >= 0) ? ITEM_HEADER : ITEM_DEFAULT; - } - - @Override - public long getItemId(int position) { - if (mLastNumber >= 0) { - return position == 0 ? 0 : mDataset.get(position - 1).id(); - } else { - return mDataset.get(position).id(); - } - } - - @Override - public int getItemCount() { - return mDataset.size() + (mLastNumber >= 0 ? 1 : 0); - } - - @Override - public void onChapterClick(int pos, MangaChapter chapter) { - if (mClickListener != null) { - if (mLastNumber < 0) { - mClickListener.onChapterClick(pos, mDataset.get(pos)); - } else { - if (pos == -1) { - mClickListener.onChapterClick(-1, null); - } else { - mClickListener.onChapterClick(pos - 1, mDataset.get(pos - 1)); - } - } - } - } - - @Override - public boolean onChapterLongClick(int pos, MangaChapter chapter) { - if (mClickListener != null) { - if (mLastNumber < 0) { - return mClickListener.onChapterLongClick(pos, mDataset.get(pos)); - } else { - if (pos == -1) { - return mClickListener.onChapterLongClick(-1, null); - } else { - return mClickListener.onChapterLongClick(pos - 1, mDataset.get(pos - 1)); - } - } - } - return false; - } - - static class HeaderHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - - private final OnChapterClickListener mListener; - final TextView textViewTitle; - final TextView textViewSubtitle; - - HeaderHolder(View itemView, OnChapterClickListener listener) { - super(itemView); - itemView.findViewById(R.id.button_positive).setOnClickListener(this); - mListener = listener; - textViewTitle = itemView.findViewById(R.id.textView_title); - textViewSubtitle = itemView.findViewById(R.id.textView_subtitle); - } - - @Override - public void onClick(View v) { - mListener.onChapterClick(-1, null); - } - } - - static class ChapterHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { - - private final OnChapterClickListener mListener; - - ChapterHolder(View itemView, OnChapterClickListener listener) { - super(itemView); - itemView.setOnClickListener(this); - itemView.setOnLongClickListener(this); - mListener = listener; - } - - public TextView getTextView() { - return (TextView) itemView; - } - - @Override - public void onClick(View v) { - mListener.onChapterClick(getAdapterPosition(), null); - } - - @Override - public boolean onLongClick(View view) { - return mListener.onChapterLongClick(getAdapterPosition(), null); - } - } -} - diff --git a/app/src/main/java/org/nv95/openmanga/adapters/DirAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/DirAdapter.java deleted file mode 100644 index 9e3128f7..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/DirAdapter.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; - -import java.io.File; -import java.util.ArrayList; - -/** - * Created by nv95 on 01.01.16. - */ -public class DirAdapter extends BaseAdapter { - - private final Context mContext; - private final ArrayList mFiles; - private File mCurrentDir; - private final Drawable[] mIcons; - - public DirAdapter(Context context, File dir) { - mContext = context; - mFiles = new ArrayList<>(); - mIcons = LayoutUtils.getThemedIcons( - context, - R.drawable.ic_directory_dark, - R.drawable.ic_directory_null_dark - ); - setCurrentDir(dir); - } - - @NonNull - public File getCurrentDir() { - return mCurrentDir; - } - - public void setCurrentDir(@NonNull File dir) { - mCurrentDir = dir; - mFiles.clear(); - File[] list = dir.listFiles(); - if (list != null) { - for (File o : list) { - if (o.isDirectory()) { - mFiles.add(o); - } - } - } - } - - @Override - public int getCount() { - return mFiles.size(); - } - - @Override - public File getItem(int position) { - return mFiles.get(position); - } - - @Override - public long getItemId(int position) { - return mFiles.get(position).hashCode(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - TextView textView = (TextView) (convertView == null ? View.inflate(mContext, R.layout.item_dir, null) : convertView); - File f = getItem(position); - textView.setText(f.getName()); - textView.setCompoundDrawablesWithIntrinsicBounds(f.canWrite() ? mIcons[0]: mIcons[1], null, null, null); - return textView; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/DownloadsAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/DownloadsAdapter.java deleted file mode 100644 index c7a41278..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/DownloadsAdapter.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2016 Vasily Nikitin - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see http://www.gnu.org/licenses/. - */ - -package org.nv95.openmanga.adapters; - -import android.annotation.SuppressLint; -import android.content.ComponentName; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.graphics.drawable.Drawable; -import android.os.IBinder; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.DownloadInfo; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.services.SaveService; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.PausableAsyncTask.ExStatus; - -import java.util.ArrayList; - -/** - * Created by nv95 on 03.01.16. - */ -public class DownloadsAdapter extends RecyclerView.Adapter - implements ServiceConnection, SaveService.OnSaveProgressListener, View.OnClickListener { - - private final Intent mIntent; - @Nullable - private SaveService.SaveServiceBinder mBinder; - private final RecyclerView mRecyclerView; - private final ArrayList mItemsIds; - private final Drawable[] mIcons; - - public DownloadsAdapter(RecyclerView recyclerView) { - mItemsIds = new ArrayList<>(); - mIntent = new Intent(recyclerView.getContext(), SaveService.class); - mBinder = null; - mRecyclerView = recyclerView; - mIcons = LayoutUtils.getThemedIcons(recyclerView.getContext(), R.drawable.ic_resume_darker, R.drawable.ic_pause_darker, R.drawable.ic_cancel_darker); - setHasStableIds(true); - } - - public void enable() { - mRecyclerView.getContext().bindService(mIntent, this, 0); - } - - public void disable() { - if (mBinder != null) { - mBinder.removeListener(this); - } - mRecyclerView.getContext().unbindService(this); - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - mBinder = (SaveService.SaveServiceBinder) service; - mItemsIds.clear(); - mItemsIds.addAll(mBinder.getAllIds()); - mBinder.addListener(this); - notifyDataSetChanged(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mBinder = null; - notifyDataSetChanged(); - } - - @Override - public DownloadHolder onCreateViewHolder(ViewGroup parent, int viewType) { - DownloadHolder holder = new DownloadHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_download, parent, false)); - holder.imageRemove.setOnClickListener(this); - holder.imageRemove.setImageDrawable(mIcons[2]); - holder.imageRemove.setTag(holder); - holder.imagePause.setOnClickListener(this); - holder.imagePause.setImageDrawable(mIcons[1]); - holder.imagePause.setTag(holder); - holder.imageResume.setOnClickListener(this); - holder.imageResume.setImageDrawable(mIcons[0]); - holder.imageResume.setTag(holder); - return holder; - } - - @Override - public void onBindViewHolder(DownloadHolder holder, int position) { - if (mBinder != null) { - int id = (int) getItemId(position); - holder.fill(mBinder.getItemById(id), mBinder.getTaskStatus(id)); - } - } - - @Override - public int getItemCount() { - return mBinder != null ? mBinder.getTaskCount() : 0; - } - - @Override - public long getItemId(int position) { - if (mItemsIds.size() < position + 1) { - mItemsIds.clear(); - mItemsIds.addAll(mBinder.getAllIds()); - } - return mItemsIds.get(position); - } - - @Override - public void onProgressUpdated(int id) { - int position = mItemsIds.indexOf(id); - RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(position); - if (mBinder != null && holder != null && holder instanceof DownloadHolder) { - DownloadInfo item = mBinder.getItemById(id); - if (item.pos < item.max) { - ((DownloadHolder) holder).updateProgress( - item.pos * 100 + item.getChapterProgressPercent(), - item.max * 100, - item.chaptersProgresses[item.pos], - item.chaptersSizes[item.pos], - item.chapters.get(item.pos).name - ); - } else { - notifyItemChanged(position); - } - } - } - - @Override - public void onDataUpdated(int id) { - /*int pos = mItemsIds.indexOf(id); - if (pos >= 0) { - notifyItemChanged(pos); - } else { - onDataUpdated(); - }*/ - onDataUpdated(); - } - - @Override - public void onDataUpdated() { - mItemsIds.clear(); - mItemsIds.addAll(mBinder.getAllIds()); - notifyDataSetChanged(); - } - - public void setTaskPaused(boolean paused) { - if (mBinder != null) { - if (paused) { - mBinder.pauseAll(); - } else { - mBinder.resumeAll(); - } - } - } - - @Override - public void onClick(View view) { - if (mBinder == null) return; - Object tag = view.getTag(); - if (tag != null && tag instanceof DownloadHolder) { - DownloadHolder holder = (DownloadHolder) tag; - final int pos = holder.getAdapterPosition(); - switch (view.getId()) { - case R.id.buttonRemove: - new AlertDialog.Builder(view.getContext()) - .setMessage(view.getContext().getString(R.string.download_unqueue_confirm, holder.mTextViewTitle.getText())) - .setPositiveButton(R.string.action_remove, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (mBinder != null) { - mBinder.cancelAndRemove((int) getItemId(pos)); - } - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create() - .show(); - break; - case R.id.buttonPause: - mBinder.setPaused((int) getItemId(pos), true); - notifyItemChanged(pos); - break; - case R.id.buttonResume: - mBinder.setPaused((int) getItemId(pos), false); - notifyItemChanged(pos); - break; - } - } - } - - static class DownloadHolder extends RecyclerView.ViewHolder { - - private final ImageView mImageView; - private final TextView mTextViewTitle; - private final TextView mTextViewSubtitle; - private final TextView mTextViewState; - private final TextView mTextViewPercent; - private final ProgressBar mProgressBarPrimary; - private final ProgressBar mProgressBarSecondary; - final ImageView imageRemove; - final ImageView imagePause; - final ImageView imageResume; - - DownloadHolder(View itemView) { - super(itemView); - mImageView = itemView.findViewById(R.id.imageView); - mTextViewTitle = itemView.findViewById(R.id.textView_title); - mTextViewSubtitle = itemView.findViewById(R.id.textView_subtitle); - mTextViewState = itemView.findViewById(R.id.textView_state); - mProgressBarPrimary = itemView.findViewById(R.id.progressBar_primary); - mProgressBarSecondary = itemView.findViewById(R.id.progressBar_secondary); - mTextViewPercent = itemView.findViewById(R.id.textView_percent); - imageRemove = itemView.findViewById(R.id.buttonRemove); - imagePause = itemView.findViewById(R.id.buttonPause); - imageResume = itemView.findViewById(R.id.buttonResume); - } - - @SuppressLint("SetTextI18n") - public void fill(DownloadInfo data, ExStatus status) { - mTextViewTitle.setText(data.name); - ImageUtils.setThumbnail(mImageView, data.preview, ThumbSize.THUMB_SIZE_LIST); - switch (status) { - case PENDING: - mTextViewState.setText(R.string.queue); - imagePause.setVisibility(View.GONE); - imageResume.setVisibility(View.GONE); - imageRemove.setVisibility(View.VISIBLE); - break; - case FINISHED: - mTextViewState.setText(R.string.completed); - imagePause.setVisibility(View.GONE); - imageResume.setVisibility(View.GONE); - imageRemove.setVisibility(View.GONE); - break; - case RUNNING: - mTextViewState.setText(R.string.saving_manga); - imagePause.setVisibility(View.VISIBLE); - imageResume.setVisibility(View.GONE); - imageRemove.setVisibility(View.VISIBLE); - break; - case PAUSED: - mTextViewState.setText(R.string.paused); - imagePause.setVisibility(View.GONE); - imageResume.setVisibility(View.VISIBLE); - imageRemove.setVisibility(View.VISIBLE); - break; - } - if (data.pos < data.max) { - updateProgress(data.pos, data.max, data.chaptersProgresses[data.pos], data.chaptersSizes[data.pos], - data.chapters.get(data.pos).name); - } else { - mProgressBarPrimary.setProgress(mProgressBarPrimary.getMax()); - mProgressBarSecondary.setProgress(mProgressBarSecondary.getMax()); - mTextViewPercent.setText("100%"); - mTextViewSubtitle.setText(itemView.getContext().getString(R.string.chapters_total, data.max)); - } - } - - /** - * - * @param tPos current chapter - * @param tMax chapters count - * @param cPos current page - * @param cMax pages count - * @param subtitle chapter name - */ - @SuppressLint("SetTextI18n") - void updateProgress(int tPos, int tMax, int cPos, int cMax, String subtitle) { - mProgressBarPrimary.setMax(tMax); - mProgressBarPrimary.setProgress(tPos); - mProgressBarSecondary.setMax(cMax); - mProgressBarSecondary.setProgress(cPos); - mTextViewSubtitle.setText(subtitle); - mTextViewPercent.setText((tMax == 0 ? 0 : tPos * 100 / tMax) + "%"); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/EndlessAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/EndlessAdapter.java deleted file mode 100644 index 94b454c8..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/EndlessAdapter.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ProgressBar; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.lists.PagedList; -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * Created by nv95 on 25.01.16. - */ -public abstract class EndlessAdapter extends RecyclerView.Adapter { - - public static final int VIEW_ITEM = 1; - private static final int VIEW_PROGRESS = 0; - - private final PagedList mDataset; - private final int mVisibleThreshold = 2; - private int mLastVisibleItem, mTotalItemCount; - private boolean mLoading; - private OnLoadMoreListener mOnLoadMoreListener; - - public EndlessAdapter(PagedList dataset, RecyclerView recyclerView) { - mDataset = dataset; - attach(recyclerView); - } - - public void attach(RecyclerView recyclerView) { - recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - mTotalItemCount = LayoutUtils.getItemCount(recyclerView); - mLastVisibleItem = LayoutUtils.findLastVisibleItemPosition(recyclerView); - if (!mLoading && isLoadEnabled() && mTotalItemCount <= (mLastVisibleItem + mVisibleThreshold)) { - // End has been reached - // Do something - if (mOnLoadMoreListener != null) { - mOnLoadMoreListener.onLoadMore(); - mLoading = true; - } - } - } - }); - } - - @Override - public int getItemViewType(int position) { - return getItem(position) != null ? VIEW_ITEM : VIEW_PROGRESS; - } - - @Override - public long getItemId(int position) { - if (position == mDataset.size()) { - return 0; - } else { - return getItemId(mDataset.get(position)); - } - } - - @Override - public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == VIEW_ITEM) { - return onCreateHolder(parent); - } else { - return new ProgressViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.footer_loading, parent, false)); - } - } - - @SuppressWarnings("unchecked") - @Override - public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - T item = getItem(position); - if (item != null) { - onBindHolder((VH) holder, item, position); - } else if (holder instanceof ProgressViewHolder) { - ((ProgressViewHolder) holder).setVisible(isLoadEnabled()); - } - } - - public void setLoaded() { - mLoading = false; - } - - @Override - public int getItemCount() { - return mDataset.size() + 1; - } - - @Nullable - public T getItem(int position) { - if (position == mDataset.size()) { - return null; - } else { - return mDataset.get(position); - } - } - - public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) { - mOnLoadMoreListener = onLoadMoreListener; - } - - private boolean isLoadEnabled() { - return mDataset.size() != 0 && mDataset.isHasNext(); - } - - public abstract VH onCreateHolder(ViewGroup parent); - - public abstract void onBindHolder(VH viewHolder, T data, int position); - - public abstract long getItemId(T data); - - public interface OnLoadMoreListener { - void onLoadMore(); - } - - private static class ProgressViewHolder extends RecyclerView.ViewHolder { - private final ProgressBar mProgressBar; - - ProgressViewHolder(View v) { - super(v); - mProgressBar = v.findViewById(R.id.progressBar); - } - - public void setVisible(boolean visible) { - mProgressBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/adapters/FastHistoryAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/FastHistoryAdapter.java deleted file mode 100644 index c740ffa8..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/FastHistoryAdapter.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.HistoryMangaInfo; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.choicecontrol.OnHolderClickListener; - -/** - * Created by admin on 19.07.17. - */ - -public class FastHistoryAdapter extends RecyclerView.Adapter implements View.OnClickListener { - - private final MangaList mDataset; - private final OnHolderClickListener mClickListener; - - public FastHistoryAdapter(MangaList dataset, OnHolderClickListener clickListener) { - mDataset = dataset; - mClickListener = clickListener; - } - - @Override - public FastHistoryHolder onCreateViewHolder(ViewGroup parent, int viewType) { - FastHistoryHolder holder = new FastHistoryHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_history, parent, false)); - holder.contentLayout.setOnClickListener(this); - holder.contentLayout.setTag(holder); - return holder; - } - - @Override - public void onBindViewHolder(FastHistoryHolder holder, int position) { - MangaInfo manga = mDataset.get(position); - holder.textViewTitle.setText(manga.name); - if (manga instanceof HistoryMangaInfo) { - holder.textViewSubtitle.setText(AppHelper.getReadableDateTimeRelative(((HistoryMangaInfo) manga).timestamp)); - } else { - holder.textViewSubtitle.setText(manga.subtitle); - } - ImageUtils.setThumbnail(holder.imageView, manga.preview); - - } - - @Override - public int getItemCount() { - return mDataset.size(); - } - - @Override - public void onClick(View view) { - FastHistoryHolder holder = (FastHistoryHolder) view.getTag(); - mClickListener.onClick(holder); - } - - public MangaInfo getItem(int position) { - return mDataset.get(position); - } - - static class FastHistoryHolder extends RecyclerView.ViewHolder { - - final RelativeLayout contentLayout; - final ImageView imageView; - final TextView textViewTitle; - final TextView textViewSubtitle; - - FastHistoryHolder(View itemView) { - super(itemView); - contentLayout = itemView.findViewById(R.id.content); - imageView = itemView.findViewById(R.id.imageView); - textViewTitle = itemView.findViewById(R.id.textView_title); - textViewSubtitle = itemView.findViewById(R.id.textView_subtitle); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/FileHolderCallback.java b/app/src/main/java/org/nv95/openmanga/adapters/FileHolderCallback.java deleted file mode 100644 index 32b9069f..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/FileHolderCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.nv95.openmanga.adapters; - -/** - * Created by nv95 on 09.02.16. - */ -public interface FileHolderCallback { - void onItemClick(FileSelectAdapter.FileViewHolder holder); -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/FileSelectAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/FileSelectAdapter.java deleted file mode 100644 index 2d09200f..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/FileSelectAdapter.java +++ /dev/null @@ -1,192 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.dialogs.DirSelectDialog; -import org.nv95.openmanga.utils.LayoutUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * Created by nv95 on 09.02.16. - */ -public class FileSelectAdapter extends RecyclerView.Adapter - implements FileHolderCallback{ - - private String[] mPattern; - private final DirSelectDialog.OnDirSelectListener mCallback; - private File mCurrentDir; - private final ArrayList files; - - public FileSelectAdapter(File currentDir, @Nullable String pattern, - DirSelectDialog.OnDirSelectListener callback) { - files = new ArrayList<>(); - mCallback = callback; - mPattern = pattern == null ? null : pattern.split(";"); - setCurrentDir(currentDir); - } - - @NonNull - public File getCurrentDir() { - return mCurrentDir; - } - - public void setCurrentDir(@NonNull File dir) { - mCurrentDir = dir; - files.clear(); - File[] list = dir.listFiles(); - for (File o : list) { - if (o.isDirectory() || checkPattern(o.getName())) { - files.add(o); - } - } - Collections.sort(files, new FileSortComparator()); - files.add(0, new File(dir, "..")); - notifyDataSetChanged(); - } - - private boolean checkPattern(String name) { - if (mPattern == null) { - return true; - } - String ext = name.substring(name.lastIndexOf('.') + 1); - for (String o : mPattern) { - if (ext.equalsIgnoreCase(o)) { - return true; - } - } - return false; - } - - public boolean toParentDir() { - File parentDir = mCurrentDir.getParentFile(); - if (parentDir == null || !parentDir.canRead()) { - return false; - } - files.clear(); - File[] list = parentDir.listFiles(); - for (File o : list) { - if (o.isDirectory() || checkPattern(o.getName())) { - files.add(o); - } - } - Collections.sort(files, new FileSortComparator()); - files.add(0, new File(parentDir, "..")); - int position = files.indexOf(mCurrentDir); - mCurrentDir = parentDir; - notifyDataSetChanged(); - return true; - } - - public boolean setDirectory(File directory) { - if (!directory.canRead()) { - return false; - } - int position = files.indexOf(directory); - if (position == -1) { - setCurrentDir(directory); - return true; - } - if (directory.getName().equals("..")) { - return toParentDir(); - } - files.clear(); - File[] list = directory.listFiles(); - for (File o : list) { - if (o.isDirectory() || checkPattern(o.getName())) { - files.add(o); - } - } - Collections.sort(files, new FileSortComparator()); - files.add(0, new File(directory, "..")); - mCurrentDir = directory; - notifyDataSetChanged(); - return true; - } - - @Override - public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new FileViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_dir, parent, false), this); - } - - @Override - public void onBindViewHolder(FileViewHolder holder, int position) { - holder.fill(files.get(position)); - } - - @Override - public int getItemCount() { - return files.size(); - } - - @Override - public void onItemClick(FileViewHolder holder) { - mCallback.onDirSelected(holder.mFile); - } - - static class FileViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - private static Drawable[] icons = null; - private File mFile; - private final TextView mTextView; - private final FileHolderCallback mCallback; - - public FileViewHolder(View itemView, FileHolderCallback callback) { - super(itemView); - mCallback = callback; - mTextView = (TextView) itemView; - itemView.setOnClickListener(this); - if (icons == null) { - icons = LayoutUtils.getThemedIcons( - mTextView.getContext(), - R.drawable.ic_arrow_up, - R.drawable.ic_directory_dark, - R.drawable.ic_directory_null_dark, - R.drawable.ic_file_dark - ); - } - } - - protected void fill(File file) { - mFile = file; - mTextView.setText(file.getName()); - int icon; - if ("..".endsWith(file.getName())) { - icon = 0; - } else if (file.isDirectory()) { - icon = file.canRead() ? 1 : 2; - } else { - icon = 3; - } - mTextView.setCompoundDrawablesWithIntrinsicBounds(icons[icon], null, null, null); - } - - @Override - public void onClick(View v) { - mCallback.onItemClick(this); - } - } - - private class FileSortComparator implements Comparator { - - @Override - public int compare(File lhs, File rhs) { - if (lhs.isDirectory()) - return rhs.isDirectory() ? lhs.compareTo(rhs) : -1; - else if (rhs.isDirectory()) - return 1; - return lhs.compareTo(rhs); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/GenresSortAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/GenresSortAdapter.java deleted file mode 100644 index 0a320958..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/GenresSortAdapter.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Checkable; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; - -import java.util.ArrayList; - -/** - * Created by nv95 on 28.11.16. - */ - -public class GenresSortAdapter extends RecyclerView.Adapter implements View.OnClickListener { - - private static final int TYPE_HEADER = 0; - private static final int TYPE_SORT = 1; - private static final int TYPE_GENRE = 2; - - private final ArrayList mDataset; - private final Callback mCallback; - private int mSelGenre, mSelSort; - - public GenresSortAdapter(Callback callback) { - mDataset = new ArrayList<>(); - mCallback = callback; - mSelGenre = 0; - mSelSort = 0; - } - - public void fromProvider(Context context, MangaProvider provider) { - String[] data; - mDataset.clear(); - if (provider.hasSort()) { - data = provider.getSortTitles(context); - if (data != null) { - mDataset.add(new TypedString(context.getString(R.string.action_sort), TYPE_HEADER)); - for (int i = 0; i < data.length; i++) { - mDataset.add(new TypedString(data[i], TYPE_SORT, i)); - } - } - mSelSort = MangaProviderManager.restoreSortOrder(context, provider); - } else { - mSelSort = 0; - } - if (provider.hasGenres()) { - data = provider.getGenresTitles(context); - if (data != null) { - mDataset.add(new TypedString(context.getString( - provider instanceof FavouritesProvider - ? R.string.action_category : R.string.action_genre - ), TYPE_HEADER)); - for (int i = 0; i < data.length; i++) { - mDataset.add(new TypedString(data[i], TYPE_GENRE, i)); - } - } - } - mSelGenre = 0; - if (mDataset.size() == 0) { - mDataset.add(new TypedString(context.getString(R.string.no_options_available), TYPE_HEADER)); - } - notifyDataSetChanged(); - } - - public int getSelectedGenre() { - return mSelGenre; - } - - public int getSelectedSort() { - return mSelSort; - } - - @Override - public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - switch (viewType) { - case TYPE_SORT: - case TYPE_GENRE: - RadioCheckHolder holder = new RadioCheckHolder(inflater.inflate(R.layout.item_radiocheck, parent, false)); - holder.itemView.setOnClickListener(this); - return holder; - default: - return new TextViewHolder(inflater.inflate(R.layout.header_group, parent, false)); - } - } - - @Override - public void onBindViewHolder(TextViewHolder holder, int position) { - TypedString item = mDataset.get(position); - holder.getTextView().setText(item.data); - if (holder instanceof RadioCheckHolder) { - ((RadioCheckHolder) holder).setChecked( - item.type == TYPE_SORT && item.subPosition == mSelSort - || item.type == TYPE_GENRE && item.subPosition == mSelGenre - ); - } - } - - @Override - public int getItemCount() { - return mDataset.size(); - } - - @Override - public int getItemViewType(int position) { - return mDataset.get(position).type; - } - - private int getAbsolutePosition(int type, int subPos) { - TypedString o; - for (int i = 0; i < mDataset.size(); i++) { - o = mDataset.get(i); - if (o.type == type && o.subPosition == subPos) { - return i; - } - } - return -1; - } - - @Nullable - public String getSelectedGenreName() { - int pos = getAbsolutePosition(TYPE_GENRE, mSelGenre); - return pos == -1 ? null : mDataset.get(pos).data; - } - - @Nullable - public String getSelectedSortName() { - int pos = getAbsolutePosition(TYPE_SORT, mSelSort); - return pos == -1 ? null : mDataset.get(pos).data; - } - - @Override - public void onClick(View view) { - Object tag = view.getTag(); - if (tag != null && tag instanceof RadioCheckHolder) { - int pos = ((RadioCheckHolder) tag).getAdapterPosition(); - TypedString item = mDataset.get(pos); - if (item.type == TYPE_SORT) { - mSelSort = item.subPosition; - mCallback.onApply(mSelGenre, mSelSort, getSelectedGenreName(), item.data); - - notifyDataSetChanged(); - } else if (item.type == TYPE_GENRE) { - mSelGenre = item.subPosition; - mCallback.onApply(mSelGenre, mSelSort, item.data, getSelectedSortName()); - notifyDataSetChanged(); - } - } - } - - private static class TypedString { - final String data; - final int type; - int subPosition; - - public TypedString(String data, int type) { - this.data = data; - this.type = type; - this.subPosition = 0; - } - - public TypedString(String data, int type, int subpos) { - this.data = data; - this.type = type; - this.subPosition = subpos; - } - - @Override - public String toString() { - return data; - } - } - - private static class RadioCheckHolder extends TextViewHolder implements Checkable { - - RadioCheckHolder(View itemView) { - super(itemView); - itemView.setTag(this); - } - - @Override - public void setChecked(boolean b) { - ((Checkable) itemView).setChecked(b); - } - - @Override - public boolean isChecked() { - return ((Checkable) itemView).isChecked(); - } - - @Override - public void toggle() { - ((Checkable) itemView).toggle(); - } - } - - static class TextViewHolder extends RecyclerView.ViewHolder { - - TextViewHolder(View itemView) { - super(itemView); - } - - TextView getTextView() { - return (TextView) itemView; - } - } - - public interface Callback { - void onApply(int genre, int sort, @Nullable String genreName, @Nullable String sortName); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/HeaderedAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/HeaderedAdapter.java deleted file mode 100644 index 49e608a3..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/HeaderedAdapter.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; - -/** - * Created by nv95 on 09.08.16. - */ - -public abstract class HeaderedAdapter extends RecyclerView.Adapter { - - private int mHeaders = 0; - private ArrayList mHeaderViews = new ArrayList<>(); - - @Override - public final int getItemCount() { - return mHeaders + getDataItemCount(); - } - - @Override - public int getItemViewType(int position) { - if (position < mHeaders) { - return position; - } else { - return mHeaders + getDataItemType(position - mHeaders); - } - } - - @Override - public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType < mHeaders) { - return new HeaderHolder(mHeaderViews.get(viewType)); - } else { - return onCreateDataViewHolder(parent, viewType - mHeaders); - } - } - - @Override - public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if (!(holder instanceof HeaderedAdapter.HeaderHolder)) { - //noinspection unchecked - onBindDataViewHolder((VH) holder, position - mHeaders); - } - } - - public void addHeader(View header, int position) { - mHeaderViews.add(position, header); - mHeaders++; - notifyItemInserted(position); - } - - public abstract int getDataItemCount(); - - public int getDataItemType(int position) { - return 0; - } - - public abstract VH onCreateDataViewHolder(ViewGroup parent, int viewType); - - public abstract void onBindDataViewHolder(VH holder, int position); - - public int getHeadersCount() { - return mHeaders; - } - - private static class HeaderHolder extends RecyclerView.ViewHolder { - - public HeaderHolder(View itemView) { - super(itemView); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/adapters/MangaListAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/MangaListAdapter.java deleted file mode 100755 index 70a66cb1..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/MangaListAdapter.java +++ /dev/null @@ -1,257 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Checkable; -import android.widget.ImageView; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.PreviewActivity2; -import org.nv95.openmanga.components.RatingView; -import org.nv95.openmanga.dialogs.PreviewDialog; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.lists.PagedList; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.QuickReadTask; -import org.nv95.openmanga.utils.choicecontrol.ModalChoiceController; -import org.nv95.openmanga.utils.choicecontrol.OnHolderClickListener; - -/** - * Created by nv95 on 30.09.15. - */ -public class MangaListAdapter extends EndlessAdapter { - - private boolean mGrid; - private ThumbSize mThumbSize; - @Nullable - private OnItemLongClickListener mOnItemLongClickListener; - private final ModalChoiceController mChoiceController; - - public MangaListAdapter(PagedList dataset, RecyclerView recyclerView) { - super(dataset, recyclerView); - mChoiceController = new ModalChoiceController(this); - if (MangaViewHolder.PADDING_4 == 0) { - MangaViewHolder.PADDING_4 = LayoutUtils.DpToPx(recyclerView.getResources(), 4); - MangaViewHolder.PADDING_16 = LayoutUtils.DpToPx(recyclerView.getResources(), 16); - MangaViewHolder.HEIGHT_68 = LayoutUtils.DpToPx(recyclerView.getResources(), 68); - MangaViewHolder.HEIGHT_42 = LayoutUtils.DpToPx(recyclerView.getResources(), 42); - } - } - - public boolean setGrid(boolean grid) { - if (mGrid != grid) { - mGrid = grid; - notifyDataSetChanged(); - return true; - } else { - return false; - } - } - - public ModalChoiceController getChoiceController() { - return mChoiceController; - } - - public void setThumbnailsSize(@NonNull ThumbSize size) { - if (!size.equals(mThumbSize)) { - mThumbSize = size; - notifyItemRangeChanged(0, getItemCount()); - } - } - - @Override - public MangaViewHolder onCreateHolder(ViewGroup parent) { - MangaViewHolder holder = new MangaViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(mGrid ? R.layout.item_mangagrid : R.layout.item_mangalist, parent, false), mOnItemLongClickListener); - holder.setListener(mChoiceController); - - return holder; - } - - @Override - public long getItemId(MangaInfo data) { - return data.id; - } - - @Override - public void onBindHolder(MangaViewHolder viewHolder, MangaInfo data, int position) { - viewHolder.fill(data, mThumbSize, mChoiceController.isSelected(position)); - } - - static class MangaViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { - - private static int PADDING_16 = 0; - private static int PADDING_4 = 0; - private static int HEIGHT_68 = 0; - private static int HEIGHT_42 = 0; - - @Nullable - private final OnItemLongClickListener mLongClickListener; - private final TextView textViewTitle; - @Nullable - private final TextView textViewSubtitle; - private final TextView textViewSummary; - private final TextView textViewBadge; - @Nullable - private final RatingView ratingView; - private final ImageView imageView; - @Nullable - private final ImageView imageViewStatus; - private final View buttonRead; - private MangaInfo mData; - @Nullable - private OnHolderClickListener mListener; - @Nullable - private final View cellFooter; - private int viewMode; - - MangaViewHolder(final View itemView, @Nullable OnItemLongClickListener longClickListener) { - super(itemView); - textViewTitle = itemView.findViewById(R.id.textView_title); - textViewSubtitle = itemView.findViewById(R.id.textView_subtitle); - textViewSummary = itemView.findViewById(R.id.textView_summary); - textViewBadge = itemView.findViewById(R.id.textView_badge); - ratingView = itemView.findViewById(R.id.ratingView); - imageView = itemView.findViewById(R.id.imageView); - buttonRead = itemView.findViewById(R.id.buttonRead); - cellFooter = itemView.findViewById(R.id.cell_footer); - imageViewStatus = itemView.findViewById(R.id.imageView_status); - itemView.setOnClickListener(this); - mLongClickListener = longClickListener; - itemView.setOnLongClickListener(this); - buttonRead.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (itemView instanceof Checkable && ((Checkable) itemView).isChecked()) { - return; - } - new QuickReadTask(view.getContext()) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mData); - } - }); - buttonRead.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - if (itemView instanceof Checkable && ((Checkable) itemView).isChecked()) { - return false; - } - new PreviewDialog(view.getContext()) - .show(mData); - return true; - } - }); - } - - public MangaInfo getData() { - return mData; - } - - private void updateViewMode(ThumbSize thumbSize) { - int mode = 3; //large grid - if (cellFooter == null) { - mode = 0; //list - } else if (thumbSize.getWidth() <= ThumbSize.THUMB_SIZE_SMALL.getWidth()) { - mode = 1; //small grid - } else if (thumbSize.getWidth() <= ThumbSize.THUMB_SIZE_MEDIUM.getWidth()) { - mode = 2; //medium grid - } - if (viewMode == mode) { - return; - } - viewMode = mode; - switch (viewMode) { - case 0: - buttonRead.setVisibility(View.VISIBLE); - textViewSummary.setVisibility(View.VISIBLE); - textViewTitle.setMaxLines(2); - break; - case 1: - buttonRead.setVisibility(View.GONE); - textViewSummary.setVisibility(View.GONE); - textViewTitle.setMaxLines(2); - cellFooter.getLayoutParams().height = HEIGHT_42; - cellFooter.setPadding(PADDING_4, PADDING_4, PADDING_4, PADDING_4); - break; - case 2: - buttonRead.setVisibility(View.GONE); - textViewTitle.setMaxLines(1); - textViewSummary.setVisibility(View.VISIBLE); - cellFooter.getLayoutParams().height = HEIGHT_68; - cellFooter.setPadding(PADDING_4, PADDING_4, PADDING_4, PADDING_4); - break; - case 3: - buttonRead.setVisibility(View.VISIBLE); - textViewSummary.setVisibility(View.VISIBLE); - textViewTitle.setMaxLines(1); - cellFooter.getLayoutParams().height = HEIGHT_68; - cellFooter.setPadding(PADDING_16, PADDING_16, PADDING_16, PADDING_16); - } - } - - public void fill(MangaInfo data, ThumbSize thumbSize, boolean checked) { - mData = data; - updateViewMode(thumbSize); - if (itemView instanceof Checkable) { - ((Checkable) itemView).setChecked(checked); - } - textViewTitle.setText(mData.name); - if (textViewSubtitle != null) { - if (TextUtils.isEmpty(mData.subtitle)) { - textViewSubtitle.setVisibility(View.GONE); - } else { - textViewSubtitle.setText(mData.subtitle); - textViewSubtitle.setVisibility(View.VISIBLE); - } - } - textViewSummary.setText(mData.genres); - if (ratingView != null) { - ratingView.setRating(mData.rating); - } - ImageUtils.setThumbnail(imageView, data.preview, thumbSize); - if (imageViewStatus != null) { - if (mData.status == MangaInfo.STATUS_UNKNOWN) { - imageViewStatus.setVisibility(View.INVISIBLE); - } else { - imageViewStatus.setImageResource(mData.isCompleted() ? R.drawable.ic_completed : R.drawable.ic_ongoing); - imageViewStatus.setVisibility(View.VISIBLE); - } - } - if (mData.extra == null) { - textViewBadge.setVisibility(View.GONE); - } else { - textViewBadge.setText(mData.extra); - textViewBadge.setVisibility(View.VISIBLE); - } - } - - public void setListener(@Nullable OnHolderClickListener listener) { - this.mListener = listener; - } - - @Override - public void onClick(View v) { - if (mListener == null || !mListener.onClick(this)) { - Context context = v.getContext(); - Intent intent = new Intent(context, PreviewActivity2.class); - intent.putExtras(mData.toBundle()); - context.startActivity(intent); - } - } - - @Override - public boolean onLongClick(View v) { - return !(mListener == null || !mListener.onLongClick(this)) || mLongClickListener != null && mLongClickListener.onItemLongClick(this); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/NewChaptersAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/NewChaptersAdapter.java deleted file mode 100644 index 2639b7d7..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/NewChaptersAdapter.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.content.Context; -import android.content.Intent; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.PreviewActivity2; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.ImageUtils; - -/** - * Created by nv95 on 17.04.16. - */ -public class NewChaptersAdapter extends RecyclerView.Adapter { - - private final MangaList mDataset; - - public NewChaptersAdapter(MangaList dataset) { - this.mDataset = dataset; - } - - @Override - public UpdatesHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new UpdatesHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_updates, parent, false)); - } - - @Override - public void onBindViewHolder(UpdatesHolder holder, int position) { - holder.fill(mDataset.get(position)); - } - - @Override - public int getItemCount() { - return mDataset.size(); - } - - static class UpdatesHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - private final TextView mTextViewTitle; - private final ImageView imageView; - private final TextView mTextViewBadge; - private final TextView mTextViewSubtitle; - private MangaInfo mData; - - public UpdatesHolder(View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.imageView); - mTextViewTitle = itemView.findViewById(R.id.textView_title); - mTextViewSubtitle = itemView.findViewById(R.id.textView_subtitle); - mTextViewBadge = itemView.findViewById(R.id.textView_badge); - itemView.setOnClickListener(this); - } - - public void fill(MangaInfo manga) { - mData = manga; - ImageUtils.setThumbnail(imageView, mData.preview); - mTextViewTitle.setText(mData.name); - mTextViewSubtitle.setText(mData.subtitle); - mTextViewBadge.setText(mData.extra); - } - - @Override - public void onClick(View v) { - Context context = v.getContext(); - Intent intent = new Intent(context, PreviewActivity2.class); - intent.putExtras(mData.toBundle()); - context.startActivity(intent); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/OnChapterClickListener.java b/app/src/main/java/org/nv95/openmanga/adapters/OnChapterClickListener.java deleted file mode 100644 index 0e3ca995..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/OnChapterClickListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.nv95.openmanga.adapters; - -import org.nv95.openmanga.items.MangaChapter; - -public interface OnChapterClickListener { - void onChapterClick(int pos, MangaChapter chapter); - boolean onChapterLongClick(int pos, MangaChapter chapter); -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/OnItemLongClickListener.java b/app/src/main/java/org/nv95/openmanga/adapters/OnItemLongClickListener.java deleted file mode 100644 index 45dbf3d1..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/OnItemLongClickListener.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.support.v7.widget.RecyclerView; - -/** - * Created by nv95 on 28.01.16. - */ -public interface OnItemLongClickListener { - boolean onItemLongClick(VH viewHolder); -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/ProvidersAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/ProvidersAdapter.java deleted file mode 100644 index d715f576..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/ProvidersAdapter.java +++ /dev/null @@ -1,181 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.app.Activity; -import android.content.Intent; -import android.support.v4.view.MotionEventCompat; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.settings.ProviderPreferencesActivity; -import org.nv95.openmanga.providers.staff.ProviderSummary; -import org.nv95.openmanga.utils.LayoutUtils; - -import java.util.List; - -/** - * Created by nv95 on 12.07.16. - */ - -public class ProvidersAdapter extends RecyclerView.Adapter implements View.OnClickListener { - - private static final int ITEM_VIEW = 0; - private static final int ITEM_FOOTER = 1; - private static final int ITEM_DIVIDER = 2; - - private final List mDataset; - private final String[] languages; - private final OnStartDragListener mDragListener; - private final Activity mActivity; - private int mActiveCount = 4; - - public ProvidersAdapter(Activity context, List providers, OnStartDragListener dragListener) { - mActivity = context; - mDataset = providers; - mDragListener = dragListener; - languages = context.getResources().getStringArray(R.array.languages); - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case ITEM_VIEW: - ProviderHolder pholder = new ProviderHolder( - LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_provider, parent, false), - mDragListener - ); - pholder.imageButton.setOnClickListener(this); - return pholder; - case ITEM_DIVIDER: - return new DividerHolder( - LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_provider_divider, parent, false), - mDragListener - ); - default: - return new FooterHolder( - LayoutInflater.from(parent.getContext()) - .inflate(R.layout.footer_disclaimer, parent, false) - ); - } - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if (holder instanceof ProviderHolder) { - ProviderSummary item = getItem(position); - ((ProviderHolder)holder).text1.setText(item.name); - ((ProviderHolder)holder).text2.setText(languages[item.lang]); - ((ProviderHolder)holder).imageButton.setVisibility(item.preferences == 0 ? View.GONE : View.VISIBLE); - ((ProviderHolder)holder).imageButton.setTag(item); - } - } - - private ProviderSummary getItem(int position) { - return mDataset.get(position < mActiveCount ? position : position - 1); - } - - - public void setActiveCount(int count) { - mActiveCount = count; - } - - @Override - public int getItemCount() { - return mDataset.size() + 2; - } - - @Override - public int getItemViewType(int position) { - if (position <= mDataset.size()) { - if (position == mActiveCount) { - return ITEM_DIVIDER; - } else { - return ITEM_VIEW; - } - } else { - return ITEM_FOOTER; - } - } - - public int getActiveCount() { - return mActiveCount; - } - - @Override - public void onClick(View view) { - Object tag = view.getTag(); - if (tag != null && tag instanceof ProviderSummary) { - mActivity.startActivity(new Intent(mActivity, ProviderPreferencesActivity.class).putExtra("provider", ((ProviderSummary) tag).id)); - } - } - - private static class ProviderHolder extends RecyclerView.ViewHolder implements View.OnTouchListener { - - final TextView text1; - final TextView text2; - final ImageView imageButton; - private final OnStartDragListener mStartDragListener; - - ProviderHolder(View itemView, OnStartDragListener sdl) { - super(itemView); - text1 = itemView.findViewById(android.R.id.text1); - text2 = itemView.findViewById(android.R.id.text2); - imageButton = itemView.findViewById(R.id.imageButton); - mStartDragListener = sdl; - itemView.findViewById(R.id.imageView_draghandle).setOnTouchListener(this); - if (LayoutUtils.isAppThemeDark(itemView.getContext())) { - LayoutUtils.setAllImagesColor((ViewGroup) itemView, R.color.white_overlay_85); - } - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (MotionEventCompat.getActionMasked(event) == - MotionEvent.ACTION_DOWN) { - mStartDragListener.onStartDrag(this); - } - return false; - } - } - - private static class FooterHolder extends RecyclerView.ViewHolder { - - FooterHolder(View itemView) { - super(itemView); - } - } - - public static class DividerHolder extends RecyclerView.ViewHolder implements View.OnTouchListener { - - private final OnStartDragListener mStartDragListener; - - DividerHolder(View itemView, OnStartDragListener startDragListener) { - super(itemView); - mStartDragListener = startDragListener; - if (LayoutUtils.isAppThemeDark(itemView.getContext())) { - LayoutUtils.setAllImagesColor((ViewGroup) itemView, R.color.white_overlay_85); - } - itemView.findViewById(R.id.imageView_draghandle).setOnTouchListener(this); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (MotionEventCompat.getActionMasked(event) == - MotionEvent.ACTION_DOWN) { - mStartDragListener.onStartDrag(this); - } - return false; - } - } - - public interface OnStartDragListener { - void onStartDrag(RecyclerView.ViewHolder viewHolder); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/SearchHistoryAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/SearchHistoryAdapter.java deleted file mode 100644 index 36bf59cb..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/SearchHistoryAdapter.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.StorageHelper; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.lang.ref.WeakReference; - -/** - * Created by nv95 on 02.01.16. - */ -public class SearchHistoryAdapter extends RecyclerView.Adapter { - - private static final String TABLE_NAME = "search_history"; - private final Drawable[] mIcons; - - private final StorageHelper mStorageHelper; - @Nullable - private Cursor mCursor; - @Nullable - private static WeakReference sStorageHelperRef; - @NonNull - private final OnHistoryEventListener mClickListener; - @Nullable - private WeakReference mQueryTaskRef = null; - - public SearchHistoryAdapter(Context context, @NonNull OnHistoryEventListener clickListener) { - mStorageHelper = new StorageHelper(context); - sStorageHelperRef = new WeakReference<>(mStorageHelper); - setHasStableIds(true); - mClickListener = clickListener; - mCursor = null; - mIcons = LayoutUtils.getThemedIcons(context, R.drawable.ic_history_dark, R.drawable.ic_launch_black); - } - - @Override - protected void finalize() throws Throwable { - mStorageHelper.close(); - if (sStorageHelperRef != null) { - sStorageHelperRef.clear(); - sStorageHelperRef = null; - } - super.finalize(); - } - - @Override - public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { - ItemHolder holder = new ItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_search, parent, false), mClickListener); - holder.textView.setCompoundDrawablesWithIntrinsicBounds(mIcons[0], null, null, null); - holder.imageButton.setImageDrawable(mIcons[1]); - return holder; - } - - @Override - public void onBindViewHolder(ItemHolder holder, int position) { - if (mCursor != null) { - mCursor.moveToPosition(position); - holder.fill(mCursor.getString(1)); - } - } - - @Override - public long getItemId(int position) { - if (mCursor != null) { - mCursor.moveToPosition(position); - return mCursor.getInt(0); - } else { - return 0; - } - } - - private void swapCursor(@Nullable Cursor newCursor) { - Cursor oldCursor = mCursor; - mCursor = newCursor; - if (oldCursor != null) { - oldCursor.close(); - } - notifyDataSetChanged(); - } - - public void requery(@Nullable String prefix) { - cancelTask(); - Cursor cursor = mStorageHelper.getReadableDatabase().query(TABLE_NAME, null, - TextUtils.isEmpty(prefix) ? null : "query LIKE ?", - TextUtils.isEmpty(prefix) ? null : new String[] {prefix + "%"}, null, null, null); - swapCursor(cursor); - } - - public void requeryAsync(@Nullable String prefix) { - cancelTask(); - QueryTask task = new QueryTask(this); - mQueryTaskRef = new WeakReference<>(task); - task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, prefix); - } - - private void cancelTask() { - if (mQueryTaskRef != null) { - QueryTask task = mQueryTaskRef.get(); - if (task != null && task.canCancel()) { - task.cancel(false); - } - } - mQueryTaskRef = null; - } - - @Override - public int getItemCount() { - return mCursor == null ? 0 : mCursor.getCount(); - } - - static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - - private final OnHistoryEventListener mClickListener; - private String mText; - final TextView textView; - final ImageView imageButton; - - ItemHolder(View itemView, OnHistoryEventListener listener) { - super(itemView); - mClickListener = listener; - textView = itemView.findViewById(android.R.id.text1); - imageButton = itemView.findViewById(R.id.imageButton); - imageButton.setOnClickListener(this); - textView.setOnClickListener(this); - } - - public void fill(String text) { - mText = text; - textView.setText(text); - } - - @Override - public void onClick(View view) { - mClickListener.onHistoryItemClick(mText, view.getId() != imageButton.getId()); - } - } - - public interface OnHistoryEventListener { - void onHistoryItemClick(String text, boolean apply); - } - - public static void clearHistory(Context context) { - StorageHelper storageHelper = null; - boolean reused = true; - if (sStorageHelperRef != null) { - storageHelper = sStorageHelperRef.get(); - } - if (storageHelper == null) { - storageHelper = new StorageHelper(context); - reused = false; - } - storageHelper.getWritableDatabase().delete(TABLE_NAME, null, null); - if (!reused) { - storageHelper.close(); - } - } - - public static void addToHistory(Context context, String what) { - StorageHelper storageHelper = null; - boolean reused = true; - if (sStorageHelperRef != null) { - storageHelper = sStorageHelperRef.get(); - } - if (storageHelper == null) { - storageHelper = new StorageHelper(context); - reused = false; - } - SQLiteDatabase database = storageHelper.getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put("_id", what.hashCode()); - cv.put("query", what); - int updCount = database.update(TABLE_NAME, cv, "_id=" + what.hashCode(), null); - if (updCount == 0) { - database.insert(TABLE_NAME, null, cv); - } - if (!reused) { - storageHelper.close(); - } - } - - public static void removeFromHistory(Context context, long what) { - StorageHelper storageHelper = null; - boolean reused = true; - if (sStorageHelperRef != null) { - storageHelper = sStorageHelperRef.get(); - } - if (storageHelper == null) { - storageHelper = new StorageHelper(context); - reused = false; - } - SQLiteDatabase database = storageHelper.getWritableDatabase(); - database.delete(TABLE_NAME, "_id=" + what, null); - if (!reused) { - storageHelper.close(); - } - } - - - public static int getHistorySize(Context context) { - StorageHelper storageHelper = null; - boolean reused = true; - if (sStorageHelperRef != null) { - storageHelper = sStorageHelperRef.get(); - } - if (storageHelper == null) { - storageHelper = new StorageHelper(context); - reused = false; - } - int res = StorageHelper.getRowCount( - storageHelper.getReadableDatabase(), - TABLE_NAME, - null - ); - if (!reused) { - storageHelper.close(); - } - return res; - } - - - private static class QueryTask extends WeakAsyncTask { - - QueryTask(SearchHistoryAdapter searchHistoryAdapter) { - super(searchHistoryAdapter); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected Cursor doInBackground(String... strings) { - try { - String prefix = strings.length > 0 ? strings[0] : null; - return getObject().mStorageHelper.getReadableDatabase().query(TABLE_NAME, null, - TextUtils.isEmpty(prefix) ? null : "query LIKE ?", - TextUtils.isEmpty(prefix) ? null : new String[] {prefix + "%"}, null, null, null); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(@NonNull SearchHistoryAdapter searchHistoryAdapter, Cursor cursor) { - if (cursor != null) { - searchHistoryAdapter.swapCursor(cursor); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/adapters/SearchResultsAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/SearchResultsAdapter.java deleted file mode 100644 index 85687c46..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/SearchResultsAdapter.java +++ /dev/null @@ -1,263 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.providers.staff.ProviderSummary; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.choicecontrol.ModalChoiceController; - -import java.util.ArrayList; - -/** - * Created by nv95 on 24.12.16. - */ - -public class SearchResultsAdapter extends RecyclerView.Adapter implements View.OnClickListener { - - private static final int VIEW_HEADER = 0; - private static final int VIEW_ITEM = 1; - private static final int VIEW_FOOTER = 2; - - private static final int FOOTER_NONE = 0; - private static final int FOOTER_BUTTON = 1; - private static final int FOOTER_PROGRESS = 2; - - private final ArrayList mDataset; - private boolean mGrid; - private ThumbSize mThumbSize; - private final int mVisibleThreshold = 2; - private int mLastVisibleItem, mTotalItemCount; - private boolean mLoading; - private int mFooter; - private CharSequence mFooterText; - private OnMoreEventListener mOnLoadMoreListener; - private final ModalChoiceController mChoiceController; - - public SearchResultsAdapter(RecyclerView recyclerView) { - mDataset = new ArrayList<>(); - attach(recyclerView); - mGrid = false; - mLoading = false; - mFooterText = null; - mFooter = FOOTER_NONE; - mChoiceController = new ModalChoiceController(this); - } - - public void hideFooter() { - mFooter = FOOTER_NONE; - notifyItemChanged(mDataset.size()); - } - - public void setFooterProgress() { - mFooter = FOOTER_PROGRESS; - notifyItemChanged(mDataset.size()); - } - - public void setFooterButton(CharSequence title) { - mFooter = FOOTER_BUTTON; - mFooterText = title; - notifyItemChanged(mDataset.size()); - } - - public boolean setGrid(boolean grid) { - if (mGrid != grid) { - mGrid = grid; - notifyDataSetChanged(); - return true; - } else { - return false; - } - } - - public void clearData() { - mChoiceController.clearSelection(); - mDataset.clear(); - mFooter = FOOTER_NONE; - notifyDataSetChanged(); - } - - public ModalChoiceController getChoiceController() { - return mChoiceController; - } - - public void setThumbnailsSize(@NonNull ThumbSize size) { - if (!size.equals(mThumbSize)) { - mThumbSize = size; - notifyItemRangeChanged(0, getItemCount()); - } - } - - public void attach(RecyclerView recyclerView) { - recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - SearchResultsAdapter.this.onScrolled(recyclerView); - } - }); - } - - public void onScrolled(RecyclerView recyclerView) { - mTotalItemCount = LayoutUtils.getItemCount(recyclerView); - mLastVisibleItem = LayoutUtils.findLastVisibleItemPosition(recyclerView); - if (!mLoading && isLoadEnabled() && mTotalItemCount <= (mLastVisibleItem + mVisibleThreshold)) { - if (mOnLoadMoreListener != null) { - mLoading = mOnLoadMoreListener.onLoadMore(); - } - } - } - - public void loadingComplete() { - mLoading = false; - } - - public void append(@Nullable ProviderSummary group, ArrayList data) { - if (data.isEmpty()) { - return; - } - int last = mDataset.size(); - if (group != null) { - mDataset.add(group); - } - mDataset.addAll(data); - if (last == 0) { - notifyDataSetChanged(); - } else { - notifyItemRangeInserted(last, data.size() + (group == null ? 0 : 1)); - } - } - - @Override - public int getItemViewType(int position) { - if (position == mDataset.size()) { - return VIEW_FOOTER; - } else if (position > mDataset.size()) { - throw new RuntimeException("Something wrong"); - } else { - return mDataset.get(position) instanceof MangaInfo ? VIEW_ITEM : VIEW_HEADER; - } - } - - @Nullable - public MangaInfo getItem(int pos) { - Object obj = mDataset.get(pos); - return obj instanceof MangaInfo ? (MangaInfo) obj : null; - } - - @Override - public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - switch (viewType) { - case VIEW_HEADER: - return new GroupViewHolder(inflater.inflate(R.layout.header_group, parent, false)); - case VIEW_FOOTER: - FooterViewHolder holder = new FooterViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.footer_button, parent, false)); - holder.textView.setOnClickListener(this); - return holder; - default: - MangaListAdapter.MangaViewHolder mangaHolder = new MangaListAdapter.MangaViewHolder(inflater - .inflate(mGrid ? R.layout.item_mangagrid : R.layout.item_mangalist, parent, false), null); - mangaHolder.setListener(mChoiceController); - return mangaHolder; - } - } - - @Override - public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if (holder instanceof MangaListAdapter.MangaViewHolder) { - ((MangaListAdapter.MangaViewHolder) holder).fill((MangaInfo) mDataset.get(position), mThumbSize, mChoiceController.isSelected(position)); - } else if (holder instanceof FooterViewHolder) { - ((FooterViewHolder) holder).textView.setText(mFooterText); - ((FooterViewHolder) holder).textView.setVisibility(mFooter == FOOTER_BUTTON ? View.VISIBLE : View.GONE); - ((FooterViewHolder) holder).progressBar.setVisibility(mFooter == FOOTER_PROGRESS ? View.VISIBLE : View.GONE); - } else { - ((GroupViewHolder) holder).fill((ProviderSummary) mDataset.get(position)); - } - } - - @Override - public int getItemCount() { - return mDataset.size() + 1; - } - - public void setOnLoadMoreListener(OnMoreEventListener onLoadMoreListener) { - mOnLoadMoreListener = onLoadMoreListener; - } - - public AutoSpanSizeLookup getSpanSizeLookup(int spans) { - return new AutoSpanSizeLookup(spans); - } - - private boolean isLoadEnabled() { - return mDataset.size() != 0 && mFooter == FOOTER_PROGRESS; - } - - @Override - public void onClick(View v) { - mOnLoadMoreListener.onMoreButtonClick(); - } - - public boolean hasItems() { - return !mDataset.isEmpty(); - } - - private static class FooterViewHolder extends RecyclerView.ViewHolder { - - public final ProgressBar progressBar; - public final TextView textView; - - FooterViewHolder(View v) { - super(v); - progressBar = v.findViewById(R.id.progressBar); - textView = v.findViewById(R.id.textView); - boolean light = !LayoutUtils.isAppThemeDark(v.getContext()); - textView.setBackgroundResource(light ? R.drawable.background_button : R.drawable.background_button_light); - } - } - - private static class GroupViewHolder extends RecyclerView.ViewHolder { - - private final TextView mTextView; - private ProviderSummary mData; - - public GroupViewHolder(View itemView) { - super(itemView); - mTextView = itemView.findViewById(R.id.textView); - } - - public void fill(ProviderSummary data) { - mData = data; - mTextView.setText(data.name); - } - } - - private class AutoSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { - final int mCount; - - AutoSpanSizeLookup(int mCount) { - this.mCount = mCount; - } - - @Override - public int getSpanSize(int position) { - return getItemViewType(position) == VIEW_ITEM ? 1 : mCount; - } - } - - public interface OnMoreEventListener { - void onMoreButtonClick(); - boolean onLoadMore(); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/adapters/SimpleViewPagerAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/SimpleViewPagerAdapter.java deleted file mode 100644 index 3fcb8dd4..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/SimpleViewPagerAdapter.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.support.v4.view.PagerAdapter; -import android.util.Pair; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; - -/** - * Created by unravel22 on 18.02.17. - */ - -public class SimpleViewPagerAdapter extends PagerAdapter { - - private final ArrayList> mViews; - - public SimpleViewPagerAdapter() { - mViews = new ArrayList<>(); - } - - public void addView(View view, String title) { - mViews.add(new Pair<>(view, title)); - } - - @Override - public int getCount() { - return mViews.size(); - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - View view = mViews.get(position).first; - container.addView(view); - return view; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - container.removeView((View) object); - } - - @Override - public CharSequence getPageTitle(int position) { - return mViews.get(position).second; - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return view == object; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/adapters/ThumbnailsAdapter.java b/app/src/main/java/org/nv95/openmanga/adapters/ThumbnailsAdapter.java deleted file mode 100644 index 21a91bc9..00000000 --- a/app/src/main/java/org/nv95/openmanga/adapters/ThumbnailsAdapter.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.nv95.openmanga.adapters; - -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.components.AutoHeightLayout; -import org.nv95.openmanga.components.reader.PageWrapper; -import org.nv95.openmanga.dialogs.NavigationListener; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.utils.ImageUtils; - -import java.util.List; - -/** - * Created by nv95 on 18.11.16. - */ - -public class ThumbnailsAdapter extends RecyclerView.Adapter implements View.OnClickListener { - - private final List mPages; - @Nullable - private NavigationListener mListener; - private int mCurrentPosition; - - public ThumbnailsAdapter(List pages) { - mPages = pages; - mListener = null; - } - - public void setNavigationListener(@Nullable NavigationListener listener) { - mListener = listener; - } - - public void setCurrentPosition(int pos) { - int lastPos = mCurrentPosition; - mCurrentPosition = pos; - if (lastPos != -1) { - notifyItemChanged(lastPos); - } - if (mCurrentPosition != -1) { - notifyItemChanged(mCurrentPosition); - } - } - - @Override - public ThumbHolder onCreateViewHolder(ViewGroup parent, int viewType) { - ThumbHolder holder = new ThumbHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_thumb, parent, false)); - holder.imageView.setOnClickListener(this); - return holder; - } - - @Override - public void onBindViewHolder(ThumbHolder holder, int position) { - PageWrapper pw = mPages.get(position); - if (pw.isLoaded()) { - ImageUtils.setThumbnail(holder.imageView, "file://" + pw.getFilename(), ThumbSize.THUMB_SIZE_MEDIUM); - holder.textView.setText(null); - } else { - holder.imageView.setImageResource(R.drawable.placeholder); - holder.textView.setText(String.valueOf(position + 1)); - } - holder.setSelected(position == mCurrentPosition); - holder.imageView.setTag(position); - } - - @Override - public int getItemCount() { - return mPages.size(); - } - - @Override - public void onClick(View view) { - if (mListener != null) { - int pos = (int) view.getTag(); - mListener.onPageChange(pos); - } - } - - static class ThumbHolder extends RecyclerView.ViewHolder implements View.OnTouchListener { - - final ImageView imageView; - final TextView textView; - final View selector; - private boolean mSelected; - - ThumbHolder(View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.imageView); - textView = itemView.findViewById(R.id.textView); - selector = itemView.findViewById(R.id.selector); - ((AutoHeightLayout)(itemView)).setAspectRatio(1.3f); - imageView.setOnTouchListener(this); - } - - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - selector.setVisibility(View.VISIBLE); - break; - case MotionEvent.ACTION_UP: - imageView.performClick(); - case MotionEvent.ACTION_CANCEL: - if (!mSelected) { - selector.setVisibility(View.GONE); - } - break; - } - return false; - } - - private void setSelected(boolean selected) { - mSelected = selected; - selector.setVisibility(mSelected ? View.VISIBLE : View.INVISIBLE); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/common/AppShortcutHelper.java b/app/src/main/java/org/nv95/openmanga/common/AppShortcutHelper.java new file mode 100644 index 00000000..373cdff7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/AppShortcutHelper.java @@ -0,0 +1,76 @@ +package org.nv95.openmanga.common; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.Bitmap; +import android.graphics.drawable.Icon; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.v4.graphics.drawable.IconCompat; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.TextUtils; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.storage.db.HistoryRepository; +import org.nv95.openmanga.core.storage.db.HistorySpecification; +import org.nv95.openmanga.reader.ReaderActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 31.01.18. + */ + +public final class AppShortcutHelper { + + private final Context mContext; + + public AppShortcutHelper(Context context) { + mContext = context; + } + + public void update(@Nullable HistoryRepository historyRepository) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { + return; + } + final HistoryRepository repository = historyRepository != null ? historyRepository : HistoryRepository.get(mContext); + final ArrayList list = repository.query( + new HistorySpecification() + .orderByDate(true) + .limit(5)); + updateImpl(list); + } + + @RequiresApi(api = Build.VERSION_CODES.N_MR1) + private void updateImpl(ArrayList history) { + final ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class); + if (shortcutManager == null) { + return; + } + final ArrayList shortcuts = new ArrayList<>(5); + final ComponentName activity = new ComponentName(mContext, ReaderActivity.class); + for (MangaHistory o : history) { + final ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mContext, String.valueOf(o.id)); + builder.setShortLabel(TextUtils.ellipsize(o.name, 16)); + builder.setLongLabel(o.name); + //builder.setActivity(activity); + final Bitmap bitmap = ImageUtils.getCachedImage(o.thumbnail); + if (bitmap != null) { + builder.setIcon(IconCompat.createWithAdaptiveBitmap(bitmap).toIcon()); + } else { + builder.setIcon(Icon.createWithResource(mContext, R.drawable.placeholder)); + } + final Intent intent = new Intent(ReaderActivity.ACTION_READING_CONTINUE); + intent.putExtras(o.toBundle()); + intent.setComponent(activity); + builder.setIntent(intent); + shortcuts.add(builder.build()); + } + shortcutManager.setDynamicShortcuts(shortcuts); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/CrashHandler.java b/app/src/main/java/org/nv95/openmanga/common/CrashHandler.java new file mode 100644 index 00000000..337eb38e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/CrashHandler.java @@ -0,0 +1,68 @@ +package org.nv95.openmanga.common; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.utils.ErrorUtils; + +/** + * Created by koitharu on 12.01.18. + */ + +public class CrashHandler implements Thread.UncaughtExceptionHandler { + + private final Thread.UncaughtExceptionHandler mDefaultHandler; + private final SharedPreferences mPreferences; + + public CrashHandler(Context context) { + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + mPreferences = context.getSharedPreferences("crash", Context.MODE_PRIVATE); + } + + @Override + @SuppressLint("ApplySharedPref") + public void uncaughtException(Thread t, Throwable e) { + mPreferences.edit() + .putLong("last", System.currentTimeMillis()) + .putString("class", e.getClass().getName()) + .putString("message", e.getMessage()) + .putString("stack_trace", ErrorUtils.getStackTrace(e)) + .commit(); + mDefaultHandler.uncaughtException(t, e); + } + + public void clear() { + mPreferences.edit().clear().apply(); + } + + public boolean wasCrashed() { + return mPreferences.contains("last"); + } + + @Nullable + public static CrashHandler get() { + Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); + return handler instanceof CrashHandler ? (CrashHandler) handler : null; + } + + @Nullable + public String getErrorClassName() { + return mPreferences.getString("class", null); + } + + @Nullable + public String getErrorMessage() { + return mPreferences.getString("message", null); + } + + @Nullable + public String getErrorStackTrace() { + return mPreferences.getString("stack_trace", null); + } + + public long getErrorTimestamp() { + return mPreferences.getLong("last", 0); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/DataViewHolder.java b/app/src/main/java/org/nv95/openmanga/common/DataViewHolder.java new file mode 100644 index 00000000..73913f12 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/DataViewHolder.java @@ -0,0 +1,39 @@ +package org.nv95.openmanga.common; + +import android.content.Context; +import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.View; +/** + * Created by koitharu on 29.01.18. + */ + +public abstract class DataViewHolder extends RecyclerView.ViewHolder { + + @Nullable + private D mData; + + public DataViewHolder(View itemView) { + super(itemView); + } + + @CallSuper + public void bind(D d) { + mData = d; + } + + @CallSuper + public void recycle() { + mData = null; + } + + @Nullable + protected final D getData() { + return mData; + } + + public final Context getContext() { + return itemView.getContext(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/Dismissible.java b/app/src/main/java/org/nv95/openmanga/common/Dismissible.java new file mode 100644 index 00000000..b2eee635 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/Dismissible.java @@ -0,0 +1,10 @@ +package org.nv95.openmanga.common; + +/** + * Created by koitharu on 12.01.18. + */ + +public interface Dismissible { + + void dismiss(); +} diff --git a/app/src/main/java/org/nv95/openmanga/common/NativeFragmentPagerAdapter.java b/app/src/main/java/org/nv95/openmanga/common/NativeFragmentPagerAdapter.java new file mode 100644 index 00000000..702d3ae7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/NativeFragmentPagerAdapter.java @@ -0,0 +1,113 @@ +package org.nv95.openmanga.common; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; +/** + * Created by koitharu on 18.01.18. + */ + +public abstract class NativeFragmentPagerAdapter extends PagerAdapter { + + private final FragmentManager mFragmentManager; + private Fragment mCurrentPrimaryItem = null; + + public NativeFragmentPagerAdapter(FragmentManager fm) { + mFragmentManager = fm; + } + + /** + * Return the Fragment associated with a specified position. + */ + @NonNull + public abstract Fragment getItem(int position); + + @NonNull + @SuppressWarnings("ReferenceEquality") + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + final FragmentTransaction transaction = mFragmentManager.beginTransaction(); + + final long itemId = getItemId(position); + + // Do we already have this fragment? + String name = makeFragmentName(container.getId(), itemId); + Fragment fragment = mFragmentManager.findFragmentByTag(name); + if (fragment != null) { + transaction.attach(fragment); + } else { + fragment = getItem(position); + transaction.add(container.getId(), fragment, + makeFragmentName(container.getId(), itemId)); + } + if (fragment != mCurrentPrimaryItem) { + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + } + transaction.commitAllowingStateLoss(); + return fragment; + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + mFragmentManager.beginTransaction() + .detach((Fragment)object) + .commitAllowingStateLoss(); + } + + @SuppressWarnings("ReferenceEquality") + @Override + public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + Fragment fragment = (Fragment)object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + mCurrentPrimaryItem = fragment; + } + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return ((Fragment)object).getView() == view; + } + + @Override + public Parcelable saveState() { + return null; + } + + @Override + public void restoreState(Parcelable state, ClassLoader loader) { + } + + @Override + public int getItemPosition(@NonNull Object object) { + return POSITION_NONE; + } + + /** + * Return a unique identifier for the item at the given position. + * + *

The default implementation returns the given position. + * Subclasses should override this method if the positions of items can change.

+ * + * @param position Position within this adapter + * @return Unique identifier for the item at position + */ + public long getItemId(int position) { + return position; + } + + private static String makeFragmentName(int viewId, long id) { + return "android:switcher:" + viewId + ":" + id; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/NaturalOrderComparator.java b/app/src/main/java/org/nv95/openmanga/common/NaturalOrderComparator.java new file mode 100644 index 00000000..c9650961 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/NaturalOrderComparator.java @@ -0,0 +1,134 @@ +/* + NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java. + Copyright (C) 2003 by Pierre-Luc Paour + + Based on the C version by Martin Pool, of which this is more or less a straight conversion. + Copyright (C) 2000 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + */ +package org.nv95.openmanga.common; + +import java.util.Comparator; + +public class NaturalOrderComparator implements Comparator { + + private int compareRight(String a, String b) { + int bias = 0, ia = 0, ib = 0; + + // The longest run of digits wins. That aside, the greatest + // value wins, but we can't know that it will until we've scanned + // both numbers to know that they have the same magnitude, so we + // remember it in BIAS. + for (; ; ia++, ib++) { + char ca = charAt(a, ia); + char cb = charAt(b, ib); + + if (!Character.isDigit(ca) && !Character.isDigit(cb)) { + return bias; + } + if (!Character.isDigit(ca)) { + return -1; + } + if (!Character.isDigit(cb)) { + return +1; + } + if (ca == 0 && cb == 0) { + return bias; + } + + if (bias == 0) { + if (ca < cb) { + bias = -1; + } else if (ca > cb) { + bias = +1; + } + } + } + } + + public int compare(T o1, T o2) { + final String a = objectToString(o1); + final String b = objectToString(o2); + + int ia = 0, ib = 0; + int nza = 0, nzb = 0; + char ca, cb; + + while (true) { + // Only count the number of zeroes leading the last number compared + nza = nzb = 0; + + ca = charAt(a, ia); + cb = charAt(b, ib); + + // skip over leading spaces or zeros + while (Character.isSpaceChar(ca) || ca == '0') { + if (ca == '0') { + nza++; + } else { + // Only count consecutive zeroes + nza = 0; + } + + ca = charAt(a, ++ia); + } + + while (Character.isSpaceChar(cb) || cb == '0') { + if (cb == '0') { + nzb++; + } else { + // Only count consecutive zeroes + nzb = 0; + } + + cb = charAt(b, ++ib); + } + + // Process run of digits + if (Character.isDigit(ca) && Character.isDigit(cb)) { + int bias = compareRight(a.substring(ia), b.substring(ib)); + if (bias != 0) { + return bias; + } + } + + if (ca == 0 && cb == 0) { + // The strings compare the same. Perhaps the caller + // will want to call strcmp to break the tie. + return nza - nzb; + } + if (ca < cb) { + return -1; + } + if (ca > cb) { + return +1; + } + + ++ia; + ++ib; + } + } + + protected String objectToString(T obj) { + return obj.toString(); + } + + private static char charAt(String s, int i) { + return i >= s.length() ? 0 : s.charAt(i); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/common/NotificationHelper.java b/app/src/main/java/org/nv95/openmanga/common/NotificationHelper.java new file mode 100644 index 00000000..0434afa6 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/NotificationHelper.java @@ -0,0 +1,146 @@ +package org.nv95.openmanga.common; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.media.ThumbnailUtils; +import android.os.Build; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.annotation.StringRes; +import android.support.v4.app.NotificationCompat; + +import org.nv95.openmanga.R; + +/** + * Created by koitharu on 25.01.18. + */ + +public final class NotificationHelper { + + private int mId; + private final Resources mResources; + private final NotificationManager mManager; + private final NotificationCompat.Builder mBuilder; + + public NotificationHelper(Context context, String channelId, @StringRes int channelName) { + this(context, 0, channelId, channelName); + nextId(); + } + + public NotificationHelper(Context context, int id, String channelId, @StringRes int channelName) { + mId = id; + String mChannelId = channelId; + mResources = context.getResources(); + mBuilder = new NotificationCompat.Builder(context, mChannelId); + mManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(context, channelId, channelName); + } + mBuilder.setCategory(NotificationCompat.CATEGORY_SERVICE); + } + + public Notification get() { + return mBuilder.build(); + } + + public int getId() { + return mId; + } + + public void setId(int id) { + mId = id; + } + + public void update() { + mManager.notify(mId, get()); + } + + public void setProgress(int progress, int max) { + mBuilder.setProgress(max, progress, false); + } + + public void setIndeterminate() { + mBuilder.setProgress(0, 0, true); + } + + public void removeProgress() { + mBuilder.setProgress(0, 0, false); + } + + public void setTitle(String title) { + mBuilder.setContentTitle(title); + } + + public void setTitle(@StringRes int title) { + mBuilder.setContentTitle(mResources.getString(title)); + } + + public void setText(String text) { + mBuilder.setContentText(text); + } + + public void setText(@StringRes int text) { + mBuilder.setContentText(mResources.getString(text)); + } + + public void setIcon(@DrawableRes int icon) { + mBuilder.setSmallIcon(icon); + } + + public void setOngoing() { + mBuilder.setOngoing(true); + mBuilder.setAutoCancel(false); + } + + public void setAutoCancel() { + mBuilder.setOngoing(false); + mBuilder.setAutoCancel(true); + } + + public void nextId() { + mId = (int) (System.currentTimeMillis() % Integer.MAX_VALUE); + } + + public void setImage(@Nullable Bitmap bitmap) { + if (bitmap == null) { + mBuilder.setLargeIcon(null); + } else { + final int width = mResources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width); + final int height = mResources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height); + final Bitmap thumb = ThumbnailUtils.extractThumbnail(bitmap, width, height); + mBuilder.setLargeIcon(thumb); + } + } + + public void setSubText(@StringRes int subText) { + mBuilder.setSubText(mResources.getString(subText)); + } + + public void clearActions() { + mBuilder.mActions.clear(); + } + + public void addCancelAction(PendingIntent pendingIntent) { + mBuilder.addAction(R.drawable.sym_cancel, mResources.getString(android.R.string.cancel), pendingIntent); + } + + public void dismiss() { + mManager.cancel(mId); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private void createChannel(Context context, String channelId, @StringRes int channelName) { + final NotificationChannel channel = new NotificationChannel( + channelId, + context.getString(channelName), + NotificationManager.IMPORTANCE_DEFAULT + ); + mManager.createNotificationChannel(channel); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/OemBadgeHelper.java b/app/src/main/java/org/nv95/openmanga/common/OemBadgeHelper.java new file mode 100644 index 00000000..3aeee71f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/OemBadgeHelper.java @@ -0,0 +1,584 @@ +package org.nv95.openmanga.common; + +import android.annotation.SuppressLint; +import android.content.AsyncQueryHandler; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.utils.IntentUtils; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Created by koitharu on 30.01.18. + */ + +public final class OemBadgeHelper { + + private final Context mContext; + private ComponentName mComponentName; + @Nullable + private final Badger mBadger; + + public OemBadgeHelper(@NonNull Context context) { + mContext = context.getApplicationContext(); + mBadger = initBadger(); + } + + interface Badger { + + boolean executeBadge(int badgeCount) throws Exception; + } + + private class AdwHomeBadger implements Badger { + + private static final String INTENT_UPDATE_COUNTER = "org.adw.launcher.counter.SEND"; + private static final String PACKAGENAME = "PNAME"; + private static final String CLASSNAME = "CNAME"; + private static final String COUNT = "COUNT"; + + public boolean executeBadge(int badgeCount) { + final Intent intent = new Intent(INTENT_UPDATE_COUNTER); + intent.putExtra(PACKAGENAME, mComponentName.getPackageName()); + intent.putExtra(CLASSNAME, mComponentName.getClassName()); + intent.putExtra(COUNT, badgeCount); + return IntentUtils.sendBroadcastSafely(mContext, intent); + } + } + + private class ApexHomeBadger implements Badger { + + private static final String INTENT_UPDATE_COUNTER = "com.anddoes.launcher.COUNTER_CHANGED"; + private static final String PACKAGENAME = "package"; + private static final String COUNT = "count"; + private static final String CLASS = "class"; + + public boolean executeBadge(int badgeCount) { + final Intent intent = new Intent(INTENT_UPDATE_COUNTER); + intent.putExtra(PACKAGENAME, mComponentName.getPackageName()); + intent.putExtra(COUNT, badgeCount); + intent.putExtra(CLASS, mComponentName.getClassName()); + return IntentUtils.sendBroadcastSafely(mContext, intent); + } + } + + private class AsusHomeBadger implements Badger { + + private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE"; + private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count"; + private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name"; + private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name"; + + public boolean executeBadge(int badgeCount) { + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount); + intent.putExtra(INTENT_EXTRA_PACKAGENAME, mComponentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, mComponentName.getClassName()); + intent.putExtra("badge_vip_count", 0); + return IntentUtils.sendBroadcastSafely(mContext, intent); + } + } + + private class DefaultBadger implements Badger { + + private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE"; + private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count"; + private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name"; + private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name"; + + public boolean executeBadge(int badgeCount) { + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount); + intent.putExtra(INTENT_EXTRA_PACKAGENAME, mComponentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, mComponentName.getClassName()); + mContext.sendBroadcast(intent); + return true; + } + } + + private class HuaweiHomeBadger implements Badger { + + public boolean executeBadge(int badgeCount) { + final Bundle localBundle = new Bundle(3); + localBundle.putString("package", mContext.getPackageName()); + localBundle.putString("class", mComponentName.getClassName()); + localBundle.putInt("badgenumber", badgeCount); + mContext.getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), + "change_badge", null, localBundle); + return true; + } + } + + private class NewHtcHomeBadger implements Badger { + + private static final String INTENT_UPDATE_SHORTCUT = "com.htc.launcher.action.UPDATE_SHORTCUT"; + private static final String INTENT_SET_NOTIFICATION = "com.htc.launcher.action.SET_NOTIFICATION"; + private static final String PACKAGENAME = "packagename"; + private static final String COUNT = "count"; + private static final String EXTRA_COMPONENT = "com.htc.launcher.extra.COMPONENT"; + private static final String EXTRA_COUNT = "com.htc.launcher.extra.COUNT"; + + public boolean executeBadge(int badgeCount) { + final Intent intent1 = new Intent(INTENT_SET_NOTIFICATION); + intent1.putExtra(EXTRA_COMPONENT, mComponentName.flattenToShortString()); + intent1.putExtra(EXTRA_COUNT, badgeCount); + + final Intent intent = new Intent(INTENT_UPDATE_SHORTCUT); + intent.putExtra(PACKAGENAME, mComponentName.getPackageName()); + intent.putExtra(COUNT, badgeCount); + + return IntentUtils.sendBroadcastSafely(mContext, intent1) || + IntentUtils.sendBroadcastSafely(mContext, intent); + } + } + + private class NovaHomeBadger implements Badger { + + private static final String CONTENT_URI = "content://com.teslacoilsw.notifier/unread_count"; + private static final String COUNT = "count"; + private static final String TAG = "tag"; + + public boolean executeBadge(int badgeCount) { + final ContentValues contentValues = new ContentValues(2); + contentValues.put(TAG, mComponentName.getPackageName() + "/" + mComponentName.getClassName()); + contentValues.put(COUNT, badgeCount); + mContext.getContentResolver().insert(Uri.parse(CONTENT_URI), contentValues); + return true; + } + } + + private class OPPOHomeBadger implements Badger { + + private static final String PROVIDER_CONTENT_URI = "content://com.android.badge/badge"; + private static final String INTENT_ACTION = "com.oppo.unsettledevent"; + private static final String INTENT_EXTRA_PACKAGENAME = "pakeageName"; + private static final String INTENT_EXTRA_BADGE_COUNT = "number"; + private static final String INTENT_EXTRA_BADGE_UPGRADENUMBER = "upgradeNumber"; + private static final String INTENT_EXTRA_BADGEUPGRADE_COUNT = "app_badge_count"; + private int ROMVERSION = -1; + + @Override + public boolean executeBadge(int badgeCount) { + if (badgeCount == 0) { + badgeCount = -1; + } + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_PACKAGENAME, mComponentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount); + intent.putExtra(INTENT_EXTRA_BADGE_UPGRADENUMBER, badgeCount); + if (IntentUtils.sendBroadcastSafely(mContext, intent)) { + return true; + } + int version = getSupportVersion(); + if (version == 6) { + try { + final Bundle extras = new Bundle(); + extras.putInt(INTENT_EXTRA_BADGEUPGRADE_COUNT, badgeCount); + mContext.getContentResolver().call(Uri.parse(PROVIDER_CONTENT_URI), + "setAppBadgeCount", null, extras); + return true; + } catch (Throwable ignore) { + } + } + return false; + } + + private int getSupportVersion() { + int i = ROMVERSION; + if (i >= 0) { + return ROMVERSION; + } + try { + i = (Integer) executeClassLoad(getClass("com.color.os.ColorBuild"), "getColorOSVERSION", null, null); + } catch (Exception e) { + i = 0; + } + if (i == 0) { + try { + String str = getSystemProperty(); + if (str.startsWith("V1.4")) { + return 3; + } + if (str.startsWith("V2.0")) { + return 4; + } + if (str.startsWith("V2.1")) { + return 5; + } + } catch (Exception ignored) { + + } + } + ROMVERSION = i; + return ROMVERSION; + } + + private Object executeClassLoad(Class cls, String str, Class[] clsArr, Object[] objArr) { + Object obj = null; + if (!(cls == null || checkObjExists(str))) { + Method method = getMethod(cls, str, clsArr); + if (method != null) { + method.setAccessible(true); + try { + obj = method.invoke(null, objArr); + } catch (Throwable ignore) { + + } + } + } + return obj; + } + + @SuppressWarnings("unchecked") + @Nullable + private Method getMethod(Class cls, String str, Class[] clsArr) { + Method method = null; + if (cls == null || checkObjExists(str)) { + return null; + } + try { + cls.getMethods(); + cls.getDeclaredMethods(); + return cls.getDeclaredMethod(str, clsArr); + } catch (Exception e) { + try { + return cls.getMethod(str, clsArr); + } catch (Exception e2) { + return cls.getSuperclass() != null ? getMethod(cls.getSuperclass(), str, clsArr) : null; + } + } + } + + @Nullable + private Class getClass(String str) { + Class cls = null; + try { + cls = Class.forName(str); + } catch (ClassNotFoundException ignored) { + } + return cls; + } + + + private boolean checkObjExists(Object obj) { + return obj == null || obj.toString().equals("") || obj.toString().trim().equals("null"); + } + + + private String getSystemProperty() { + String line; + BufferedReader input = null; + try { + Process p = Runtime.getRuntime().exec("getprop " + "ro.build.version.opporom"); + input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); + line = input.readLine(); + input.close(); + } catch (Throwable ex) { + return null; + } finally { + closeQuietly(input); + } + return line; + } + } + + private class SamsungHomeBadger implements Badger { + + private static final String CONTENT_URI = "content://com.sec.badge/apps?notify=true"; + private final String[] CONTENT_PROJECTION = new String[]{"_id", "class"}; + @Nullable + private DefaultBadger mDefaultBadger = null; + + public boolean executeBadge(int badgeCount) { + try { + if (mDefaultBadger == null) { + mDefaultBadger = new DefaultBadger(); + } + mDefaultBadger.executeBadge(badgeCount); + } catch (Exception ignored) { + } + + Uri mUri = Uri.parse(CONTENT_URI); + final ContentResolver contentResolver = mContext.getContentResolver(); + Cursor cursor = null; + try { + cursor = contentResolver.query(mUri, CONTENT_PROJECTION, "package=?", new String[]{mComponentName.getPackageName()}, null); + if (cursor != null) { + String entryActivityName = mComponentName.getClassName(); + boolean entryActivityExist = false; + while (cursor.moveToNext()) { + int id = cursor.getInt(0); + ContentValues contentValues = getContentValues(mComponentName, badgeCount, false); + contentResolver.update(mUri, contentValues, "_id=?", new String[]{String.valueOf(id)}); + if (entryActivityName.equals(cursor.getString(cursor.getColumnIndex("class")))) { + entryActivityExist = true; + } + } + + if (!entryActivityExist) { + ContentValues contentValues = getContentValues(mComponentName, badgeCount, true); + contentResolver.insert(mUri, contentValues); + } + } + return true; + } catch (Exception e) { + return false; + } finally { + close(cursor); + } + } + + private ContentValues getContentValues(ComponentName componentName, int badgeCount, boolean isInsert) { + ContentValues contentValues = new ContentValues(); + if (isInsert) { + contentValues.put("package", componentName.getPackageName()); + contentValues.put("class", componentName.getClassName()); + } + + contentValues.put("badgecount", badgeCount); + + return contentValues; + } + } + + private class SonyHomeBadger implements Badger { + + private static final String INTENT_ACTION = "com.sonyericsson.home.action.UPDATE_BADGE"; + private static final String INTENT_EXTRA_PACKAGE_NAME = "com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME"; + private static final String INTENT_EXTRA_ACTIVITY_NAME = "com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME"; + private static final String INTENT_EXTRA_MESSAGE = "com.sonyericsson.home.intent.extra.badge.MESSAGE"; + private static final String INTENT_EXTRA_SHOW_MESSAGE = "com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE"; + + private static final String PROVIDER_CONTENT_URI = "content://com.sonymobile.home.resourceprovider/badge"; + private static final String PROVIDER_COLUMNS_BADGE_COUNT = "badge_count"; + private static final String PROVIDER_COLUMNS_PACKAGE_NAME = "package_name"; + private static final String PROVIDER_COLUMNS_ACTIVITY_NAME = "activity_name"; + private static final String SONY_HOME_PROVIDER_NAME = "com.sonymobile.home.resourceprovider"; + + private final Uri BADGE_CONTENT_URI = Uri.parse(PROVIDER_CONTENT_URI); + private AsyncQueryHandler mQueryHandler; + + public boolean executeBadge(int badgeCount) { + if (sonyBadgeContentProviderExists()) { + return executeBadgeByContentProvider(badgeCount); + } else { + return executeBadgeByBroadcast(badgeCount); + } + } + + private boolean executeBadgeByBroadcast(int badgeCount) { + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_PACKAGE_NAME, mComponentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, mComponentName.getClassName()); + intent.putExtra(INTENT_EXTRA_MESSAGE, String.valueOf(badgeCount)); + intent.putExtra(INTENT_EXTRA_SHOW_MESSAGE, badgeCount > 0); + return IntentUtils.sendBroadcastSafely(mContext, intent); + } + + private boolean executeBadgeByContentProvider(int badgeCount) { + if (badgeCount < 0) { + return false; + } + + if (mQueryHandler == null) { + mQueryHandler = new SafeHandler(mContext.getContentResolver()); + } + insertBadgeAsync(badgeCount, mComponentName.getPackageName(), mComponentName.getClassName()); + return true; + } + + private void insertBadgeAsync(int badgeCount, String packageName, String activityName) { + final ContentValues contentValues = new ContentValues(); + contentValues.put(PROVIDER_COLUMNS_BADGE_COUNT, badgeCount); + contentValues.put(PROVIDER_COLUMNS_PACKAGE_NAME, packageName); + contentValues.put(PROVIDER_COLUMNS_ACTIVITY_NAME, activityName); + mQueryHandler.startInsert(0, null, BADGE_CONTENT_URI, contentValues); + } + + private boolean sonyBadgeContentProviderExists() { + boolean exists = false; + ProviderInfo info = mContext.getPackageManager().resolveContentProvider(SONY_HOME_PROVIDER_NAME, 0); + if (info != null) { + exists = true; + } + return exists; + } + } + + private class XiaomiHomeBadger implements Badger { + + private static final String INTENT_ACTION = "android.intent.action.APPLICATION_MESSAGE_UPDATE"; + private static final String EXTRA_UPDATE_APP_COMPONENT_NAME = "android.intent.extra.update_application_component_name"; + private static final String EXTRA_UPDATE_APP_MSG_TEXT = "android.intent.extra.update_application_message_text"; + + public boolean executeBadge(int badgeCount) { + try { + @SuppressLint("PrivateApi") + Class miuiNotificationClass = Class.forName("android.app.MiuiNotification"); + Object miuiNotification = miuiNotificationClass.newInstance(); + Field field = miuiNotification.getClass().getDeclaredField("messageCount"); + field.setAccessible(true); + field.set(miuiNotification, String.valueOf(badgeCount == 0 ? "" : badgeCount)); + return true; + } catch (Throwable e) { + final Intent localIntent = new Intent(INTENT_ACTION); + localIntent.putExtra(EXTRA_UPDATE_APP_COMPONENT_NAME, mComponentName.getPackageName() + "/" + mComponentName.getClassName()); + localIntent.putExtra(EXTRA_UPDATE_APP_MSG_TEXT, String.valueOf(badgeCount == 0 ? "" : badgeCount)); + return IntentUtils.sendBroadcastSafely(mContext, localIntent); + } + } + } + + private class ZukHomeBadger implements Badger { + + private final Uri CONTENT_URI = Uri.parse("content://com.android.badge/badge"); + + public boolean executeBadge(int badgeCount) { + final Bundle extra = new Bundle(1); + extra.putInt("app_badge_count", badgeCount); + try { + mContext.getContentResolver().call(CONTENT_URI, "setAppBadgeCount", null, extra); + return true; + } catch (Exception e) { + return false; + } + } + } + + private class VivoHomeBadger implements Badger { + + public boolean executeBadge(int badgeCount) { + final Intent intent = new Intent("launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM"); + intent.putExtra("packageName", mContext.getPackageName()); + intent.putExtra("className", mComponentName.getClassName()); + intent.putExtra("notificationNum", badgeCount); + return IntentUtils.sendBroadcastSafely(mContext, intent); + } + } + + @MainThread + public boolean applyCount(int badgeCount) { + try { + return mBadger != null && mBadger.executeBadge(badgeCount); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private static class SafeHandler extends AsyncQueryHandler { + + private SafeHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + @Override + public void handleMessage(Message msg) { + try { + super.handleMessage(msg); + } catch (Exception ignored) { + } + } + } + + private static void close(@Nullable Cursor cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + private static void closeQuietly(@Nullable Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (Throwable ignored) { + } + + } + + @Nullable + private Badger initBadger() { + final Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName()); + if (launchIntent == null) { + return null; + } + + mComponentName = launchIntent.getComponent(); + + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + final ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + + if (resolveInfo == null || resolveInfo.activityInfo.name.toLowerCase().contains("resolver")) { + return null; + } + + final String currentHomePackage = resolveInfo.activityInfo.packageName; + + switch (currentHomePackage) { + case "com.huawei.android.launcher": + return new HuaweiHomeBadger(); + case "com.htc.launcher": + return new NewHtcHomeBadger(); + case "com.vivo.launcher": + return new VivoHomeBadger(); + case "com.zui.launcher": + return new ZukHomeBadger(); + case "com.miui.miuilite": + case "com.miui.home": + case "com.miui.miuihome": + case "com.miui.miuihome2": + case "com.miui.mihome": + case "com.miui.mihome2": + return new XiaomiHomeBadger(); + case "com.sonyericsson.home": + case "com.sonymobile.home": + return new SonyHomeBadger(); + case "com.sec.android.app.launcher": + case "com.sec.android.app.twlauncher": + return new SamsungHomeBadger(); + case "com.oppo.launcher": + return new OPPOHomeBadger(); + case "com.teslacoilsw.launcher": + return new NovaHomeBadger(); + case "com.asus.launcher": + return new AsusHomeBadger(); + case "com.anddoes.launcher": + return new ApexHomeBadger(); + case "org.adw.launcher": + case "org.adwfreak.launcher": + return new AdwHomeBadger(); + } + final String manufacturer = Build.MANUFACTURER.toUpperCase(); + switch (manufacturer) { + case "XIAOMI": + return new XiaomiHomeBadger(); + case "ZUK": + return new ZukHomeBadger(); + case "OPPO": + return new OPPOHomeBadger(); + case "VIVO": + return new VivoHomeBadger(); + } + return new DefaultBadger(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/StringJoinerCompat.java b/app/src/main/java/org/nv95/openmanga/common/StringJoinerCompat.java new file mode 100644 index 00000000..7c564439 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/StringJoinerCompat.java @@ -0,0 +1,77 @@ +package org.nv95.openmanga.common; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 06.02.18. + */ + +public final class StringJoinerCompat { + + private final String mPrefix; + private final String mDelimiter; + private final String mSuffix; + private String mEmptyValue; + @Nullable + private StringBuilder mValue; + + public StringJoinerCompat(@NonNull CharSequence delimiter) { + this(delimiter, "", ""); + } + + public StringJoinerCompat(@NonNull CharSequence delimiter, @NonNull CharSequence prefix, @NonNull CharSequence suffix) { + mPrefix = prefix.toString(); + mDelimiter = delimiter.toString(); + mSuffix = suffix.toString(); + mEmptyValue = ""; + } + + public StringJoinerCompat setEmptyValue(@NonNull CharSequence emptyValue) { + mEmptyValue = emptyValue.toString(); + return this; + } + + @Override + public String toString() { + if (mValue == null) { + return mEmptyValue; + } else { + if (mSuffix.equals("")) { + return mValue.toString(); + } else { + int initialLength = mValue.length(); + String result = mValue.append(mSuffix).toString(); + mValue.setLength(initialLength); + return result; + } + } + } + + public StringJoinerCompat add(CharSequence newElement) { + prepareBuilder().append(newElement); + return this; + } + + public StringJoinerCompat merge(@NonNull StringJoinerCompat other) { + if (other.mValue != null) { + final int length = other.mValue.length(); + StringBuilder builder = prepareBuilder(); + builder.append(other.mValue, other.mPrefix.length(), length); + } + return this; + } + + private StringBuilder prepareBuilder() { + if (mValue != null) { + mValue.append(mDelimiter); + } else { + mValue = new StringBuilder().append(mPrefix); + } + return mValue; + } + + public int length() { + return (mValue != null ? mValue.length() + mSuffix.length() : mEmptyValue.length()); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/TransitionDisplayer.java b/app/src/main/java/org/nv95/openmanga/common/TransitionDisplayer.java new file mode 100644 index 00000000..0b5b83da --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/TransitionDisplayer.java @@ -0,0 +1,47 @@ +package org.nv95.openmanga.common; + +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.support.annotation.Nullable; +import android.view.View; +import android.widget.ImageView; + +import com.nostra13.universalimageloader.core.assist.LoadedFrom; +import com.nostra13.universalimageloader.core.display.BitmapDisplayer; +import com.nostra13.universalimageloader.core.imageaware.ImageAware; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class TransitionDisplayer implements BitmapDisplayer { + + @Override + public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { + Drawable last = getDrawableFromView(imageAware.getWrappedView()); + if (last == null) { + imageAware.setImageBitmap(bitmap); + return; + } + TransitionDrawable td = new TransitionDrawable(new Drawable[]{ + last, + new BitmapDrawable(imageAware.getWrappedView().getResources(), bitmap) + }); + td.setCrossFadeEnabled(false); + imageAware.setImageDrawable(td); + td.startTransition(1000); + } + + @Nullable + private static Drawable getDrawableFromView(@Nullable View v) { + if (v == null) { + return null; + } else if (v instanceof ImageView) { + return ((ImageView) v).getDrawable(); + } else { + return null; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/TwoLevelProgress.java b/app/src/main/java/org/nv95/openmanga/common/TwoLevelProgress.java new file mode 100644 index 00000000..a79c4bf5 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/TwoLevelProgress.java @@ -0,0 +1,34 @@ +package org.nv95.openmanga.common; + +public final class TwoLevelProgress { + + private int mFirstMax = 0, mSecondMax = 0; + private int mFirstPos = 0, mSecondPos = 0; + + public void setFirstMax(int max) { + mFirstMax = max; + } + + public void setSecondMax(int max) { + mSecondMax = max; + } + + public void setFirstPos(int pos) { + mFirstPos = pos; + } + + public void setSecondPos(int pos) { + mSecondPos = pos; + } + + public int getPercent() { + if (mFirstPos > mFirstMax || mSecondPos > mSecondMax || (mSecondPos == mSecondMax && mFirstPos > 0)) { + throw new IllegalArgumentException(); + } + double firstPercent = mFirstPos * 100.0 / mFirstMax; + if (mSecondMax == 0) { + return (int) firstPercent; + } + return (int)((mSecondPos * 100.0 / mSecondMax) + (firstPercent / mSecondMax)); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/UndoHelper.java b/app/src/main/java/org/nv95/openmanga/common/UndoHelper.java new file mode 100644 index 00000000..301d9d0f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/UndoHelper.java @@ -0,0 +1,58 @@ +package org.nv95.openmanga.common; + +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.design.widget.BaseTransientBottomBar; +import android.support.design.widget.Snackbar; +import android.view.View; + +import org.nv95.openmanga.R; + +/** + * Created by koitharu on 18.01.18. + */ + +public final class UndoHelper extends BaseTransientBottomBar.BaseCallback implements View.OnClickListener { + + @NonNull + private final OnActionUndoCallback mCallback; + private final int mId; + private T mData; + + public UndoHelper(int id, @NonNull OnActionUndoCallback callback) { + mCallback = callback; + mId = id; + } + + public void snackbar(@NonNull View view, @StringRes int message, @NonNull T data, int duration) { + snackbar(view, view.getContext().getString(message), data, duration); + } + + public void snackbar(@NonNull View view, @NonNull String message, @NonNull T data, int duration) { + mData = data; + Snackbar.make(view, message, duration) + .addCallback(this) + .setAction(R.string.undo, this) + .show(); + } + + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + if (event != BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_ACTION) { + mData = null; + } + super.onDismissed(transientBottomBar, event); + } + + @Override + public void onClick(View v) { + if (mData != null) { + mCallback.onActionUndo(mId, mData); + } + } + + public interface OnActionUndoCallback { + + void onActionUndo(int actionId, T data); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/ViewPagerAdapter.java b/app/src/main/java/org/nv95/openmanga/common/ViewPagerAdapter.java new file mode 100644 index 00000000..a18d83fd --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/ViewPagerAdapter.java @@ -0,0 +1,47 @@ +package org.nv95.openmanga.common; + +import android.support.annotation.NonNull; +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class ViewPagerAdapter extends PagerAdapter { + + private final View[] mViews; + + public ViewPagerAdapter(View... pages) { + mViews = pages; + } + + @Override + public int getCount() { + return mViews.length; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + View view = mViews[position]; + container.addView(view); + return view; + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + container.removeView((View) object); + } + + @Override + public CharSequence getPageTitle(int position) { + return mViews[position].getTag().toString(); + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/WeakAsyncTask.java b/app/src/main/java/org/nv95/openmanga/common/WeakAsyncTask.java new file mode 100644 index 00000000..53619c9d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/WeakAsyncTask.java @@ -0,0 +1,99 @@ +package org.nv95.openmanga.common; + +import android.os.AsyncTask; +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 31.12.17. + */ + +@SuppressWarnings("WeakerAccess") +public abstract class WeakAsyncTask extends AsyncTask { + + private final WeakReference mObjectRef; + + public WeakAsyncTask(Obj obj) { + mObjectRef = new WeakReference<>(obj); + } + + @Nullable + protected Obj getObject() { + return mObjectRef.get(); + } + + @Override + protected final void onPostExecute(Result result) { + super.onPostExecute(result); + Obj obj = getObject(); + if (obj != null) { + onPostExecute(obj, result); + } + } + + @Override + protected final void onPreExecute() { + super.onPreExecute(); + Obj obj = getObject(); + if (obj != null) { + onPreExecute(obj); + } + } + + @Override + protected final void onProgressUpdate(Progress[] values) { + super.onProgressUpdate(values); + Obj obj = getObject(); + if (obj != null) { + onProgressUpdate(obj, values); + } + } + + @Override + protected final void onCancelled() { + super.onCancelled(); + Obj obj = getObject(); + if (obj != null) { + onTaskCancelled(obj); + } + } + + @Override + @CallSuper + protected void onCancelled(Result result) { + super.onCancelled(result);Obj obj = getObject(); + if (obj != null) { + onTaskCancelled(obj, result); + } + } + + @SafeVarargs + public final void start(Param... params) { + this.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); + } + + public boolean canCancel() { + return getStatus() != Status.FINISHED; + } + + protected void onProgressUpdate(@NonNull Obj obj, Progress[] values) {} + + protected void onPreExecute(@NonNull Obj obj) {} + + protected void onPostExecute(@NonNull Obj obj, Result result) {} + + protected void onTaskCancelled(@NonNull Obj obj) {} + + protected void onTaskCancelled(@NonNull Obj obj, Result result) {} + + public static void cancel(@Nullable WeakReference weakReference, boolean mayInterruptIfRunning) { + if (weakReference == null) return; + AsyncTask task = weakReference.get(); + if (task != null && task.getStatus() != Status.FINISHED) { + task.cancel(mayInterruptIfRunning); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/common/dialogs/AppBaseBottomSheetDialogFragment.java b/app/src/main/java/org/nv95/openmanga/common/dialogs/AppBaseBottomSheetDialogFragment.java new file mode 100644 index 00000000..cb7dcbc8 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/dialogs/AppBaseBottomSheetDialogFragment.java @@ -0,0 +1,20 @@ +package org.nv95.openmanga.common.dialogs; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.BottomSheetDialog; +import android.support.v7.app.AppCompatDialogFragment; + +import org.nv95.openmanga.common.utils.ThemeUtils; + +public abstract class AppBaseBottomSheetDialogFragment extends AppCompatDialogFragment { + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getContext(); + return new BottomSheetDialog(context, ThemeUtils.getBottomSheetTheme(getContext())); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/dialogs/BottomSheetMenuDialog.java b/app/src/main/java/org/nv95/openmanga/common/dialogs/BottomSheetMenuDialog.java new file mode 100644 index 00000000..0f449154 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/dialogs/BottomSheetMenuDialog.java @@ -0,0 +1,130 @@ +package org.nv95.openmanga.common.dialogs; + +import android.app.Activity; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.design.widget.BottomSheetDialog; +import android.support.v4.widget.TextViewCompat; +import android.support.v7.app.AppCompatDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ThemeUtils; + +import java.util.ArrayList; + +/** + * Created by koitharu on 19.01.18. + */ + +public class BottomSheetMenuDialog { + + private final AppCompatDialog mDialog; + private final ArrayList mMenuItems = new ArrayList<>(); + private final RecyclerView mRecyclerView; + @Nullable + private MenuDialog.OnMenuItemClickListener mItemClickListener = null; + private D mData; + + public BottomSheetMenuDialog(@NonNull Context context) { + mDialog = new BottomSheetDialog(context, ThemeUtils.getBottomSheetTheme(context)); + if (context instanceof Activity) { + mDialog.setOwnerActivity((Activity) context); + } + mRecyclerView = (RecyclerView) View.inflate(context, R.layout.recyclerview, null); + //mRecyclerView.setBackgroundColor(ThemeUtils.getThemeAttrColor(context, android.R.attr.colorBackground)); + mRecyclerView.setLayoutManager(new LinearLayoutManager(context)); + mDialog.setContentView(mRecyclerView); + } + + public BottomSheetMenuDialog addItem(@IdRes int id, @DrawableRes int iconId, @StringRes int title) { + return addItem(id, iconId, mDialog.getContext().getString(title)); + } + + public BottomSheetMenuDialog addItem(@IdRes int id, @DrawableRes int iconId, @NonNull String title) { + final Context context = mDialog.getContext(); + final Drawable icon = ThemeUtils.getColoredDrawable(context, iconId, android.R.attr.textColorSecondary); + mMenuItems.add(new SimpleMenuItem(id, icon, title)); + return this; + } + + public BottomSheetMenuDialog setItemClickListener(@Nullable MenuDialog.OnMenuItemClickListener listener) { + mItemClickListener = listener; + return this; + } + + public AppCompatDialog create(D data) { + mData = data; + mRecyclerView.setAdapter(new MenuAdapter()); + return mDialog; + } + + + private static class SimpleMenuItem { + + @IdRes + final int id; + final Drawable icon; + final String title; + + private SimpleMenuItem(@IdRes int id, Drawable icon, String title) { + this.id = id; + this.icon = icon; + this.title = title; + } + } + + private class MenuAdapter extends RecyclerView.Adapter { + + @Override + public MenuItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new MenuItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_menu, parent, false)); + } + + @Override + public void onBindViewHolder(MenuItemHolder holder, int position) { + SimpleMenuItem item = mMenuItems.get(position); + holder.text1.setText(item.title); + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds( + holder.text1, + item.icon, + null, null, null + ); + } + + @Override + public int getItemCount() { + return mMenuItems.size(); + } + } + + private class MenuItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final TextView text1; + + MenuItemHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + mDialog.dismiss(); + if (mItemClickListener != null) { + mItemClickListener.onMenuItemClick(mMenuItems.get(getAdapterPosition()).id, mData); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/dialogs/CustomViewDialog.java b/app/src/main/java/org/nv95/openmanga/common/dialogs/CustomViewDialog.java new file mode 100644 index 00000000..6189bf63 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/dialogs/CustomViewDialog.java @@ -0,0 +1,56 @@ +package org.nv95.openmanga.common.dialogs; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; + +/** + * Created by koitharu on 19.01.18. + */ + +public abstract class CustomViewDialog implements DialogInterface, DialogInterface.OnClickListener { + + protected final AlertDialog.Builder mDialogBuilder; + @Nullable + private AlertDialog mDialog = null; + + public CustomViewDialog(Context context) { + mDialogBuilder = new AlertDialog.Builder(context); + final View view = onCreateView(LayoutInflater.from(context)); + onViewCreated(view); + mDialogBuilder.setView(view); + } + + @NonNull + public abstract View onCreateView(@NonNull LayoutInflater inflater); + + public abstract void onViewCreated(@NonNull View view); + + public void show() { + mDialog = mDialogBuilder.create(); + mDialog.show(); + } + + @Override + public void cancel() { + if (mDialog != null) { + mDialog.cancel(); + } + } + + @Override + public void dismiss() { + if (mDialog != null) { + mDialog.dismiss(); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/dialogs/FavouriteDialog.java b/app/src/main/java/org/nv95/openmanga/common/dialogs/FavouriteDialog.java new file mode 100644 index 00000000..8e026417 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/dialogs/FavouriteDialog.java @@ -0,0 +1,98 @@ +package org.nv95.openmanga.common.dialogs; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.storage.db.CategoriesRepository; +import org.nv95.openmanga.core.storage.db.CategoriesSpecification; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; + +import java.util.ArrayList; + +/** + * Created by koitharu on 19.01.18. + */ + +public final class FavouriteDialog implements DialogInterface.OnClickListener { + + private final AlertDialog.Builder mBuilder; + private final FavouritesRepository mFavouritesRepository; + private final ArrayList mCategories; + private final MangaDetails mManga; + @Nullable + private OnFavouriteListener mListener; + + public FavouriteDialog(@NonNull Context context, @NonNull MangaDetails manga) { + mListener = null; + mBuilder = new AlertDialog.Builder(context); + mFavouritesRepository = FavouritesRepository.get(context); + mManga = manga; + final CategoriesRepository categoriesRepository = CategoriesRepository.get(context); + mCategories = categoriesRepository.query(new CategoriesSpecification().orderByDate(false)); + final MangaFavourite favourite = mFavouritesRepository.get(manga); + final String[] items = new String[mCategories.size()]; + int selected = -1; + for (int i = 0; i < mCategories.size(); i++) { + Category o = mCategories.get(i); + items[i] = o.name; + if (favourite != null && favourite.categoryId == o.id) { + selected = i; + } + } + mBuilder.setSingleChoiceItems(items, selected, this); + mBuilder.setTitle(R.string.action_favourite); + mBuilder.setNegativeButton(android.R.string.cancel, this); + mBuilder.setNeutralButton(R.string.remove, this); + mBuilder.setCancelable(true); + } + + public FavouriteDialog setListener(@Nullable OnFavouriteListener listener) { + mListener = listener; + return this; + } + + public void show() { + mBuilder.create().show(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + + break; + case DialogInterface.BUTTON_NEUTRAL: + mFavouritesRepository.remove(mManga); + if (mListener != null) { + mListener.onFavouritesChanged(mManga, null); + } + break; + default: + final Category category = mCategories.get(which); + final MangaFavourite favourite = MangaFavourite.from( + mManga, + category.id, + mManga.chapters.size() + ); + if (!mFavouritesRepository.update(favourite)) { + mFavouritesRepository.add(favourite); + } + dialog.dismiss(); + if (mListener != null) { + mListener.onFavouritesChanged(mManga, category); + } + } + } + + public interface OnFavouriteListener { + + void onFavouritesChanged(MangaDetails manga, @Nullable Category category); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/dialogs/MenuDialog.java b/app/src/main/java/org/nv95/openmanga/common/dialogs/MenuDialog.java new file mode 100644 index 00000000..92cf33bb --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/dialogs/MenuDialog.java @@ -0,0 +1,88 @@ +package org.nv95.openmanga.common.dialogs; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v7.app.AlertDialog; + +import java.util.ArrayList; + +/** + * Created by koitharu on 19.01.18. + */ + +public final class MenuDialog implements DialogInterface.OnClickListener { + + private final AlertDialog.Builder mBuilder; + private final ArrayList mMenuItems = new ArrayList<>(); + @Nullable + private OnMenuItemClickListener mItemClickListener = null; + private D mData; + + public MenuDialog(@NonNull Context context) { + mBuilder = new AlertDialog.Builder(context); + mBuilder.setCancelable(true); + } + + public MenuDialog addItem(@IdRes int id, @StringRes int title) { + mMenuItems.add(new SimpleMenuItem(id, mBuilder.getContext().getString(title))); + return this; + } + + public MenuDialog addItem(@IdRes int id, @NonNull String title) { + mMenuItems.add(new SimpleMenuItem(id, title)); + return this; + } + + public MenuDialog setTitle(@StringRes int title) { + mBuilder.setTitle(title); + return this; + } + + public MenuDialog setTitle(@Nullable CharSequence title) { + mBuilder.setTitle(title); + return this; + } + + public MenuDialog setItemClickListener(@Nullable OnMenuItemClickListener listener) { + mItemClickListener = listener; + return this; + } + + public AlertDialog create(D data) { + mData = data; + final CharSequence[] items = new CharSequence[mMenuItems.size()]; + for (int i = 0; i < items.length; i++) { + items[i] = mMenuItems.get(i).title; + } + mBuilder.setItems(items, this); + return mBuilder.create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which >= 0 && mItemClickListener != null && which < mMenuItems.size()) { + mItemClickListener.onMenuItemClick(mMenuItems.get(which).id, mData); + } + } + + private static class SimpleMenuItem { + + @IdRes + final int id; + final String title; + + private SimpleMenuItem(@IdRes int id, String title) { + this.id = id; + this.title = title; + } + } + + public interface OnMenuItemClickListener { + + void onMenuItemClick(@IdRes int id, D d); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/dialogs/TabletPopupDialog.java b/app/src/main/java/org/nv95/openmanga/common/dialogs/TabletPopupDialog.java new file mode 100644 index 00000000..2ea8ee2d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/dialogs/TabletPopupDialog.java @@ -0,0 +1,23 @@ +package org.nv95.openmanga.common.dialogs; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.StyleRes; +import android.support.v7.app.AppCompatDialog; +import android.view.Window; + +//TODO use it +class TabletPopupDialog extends AppCompatDialog { + + + public TabletPopupDialog(@NonNull Context context) { + this(context, 0); + } + + public TabletPopupDialog(@NonNull Context context, @StyleRes int theme) { + super(context, theme); + // We hide the title bar for any style configuration. Otherwise, there will be a gap + // above the bottom sheet when it is expanded. + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/AnimationUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/AnimationUtils.java new file mode 100644 index 00000000..086d66b1 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/AnimationUtils.java @@ -0,0 +1,147 @@ +package org.nv95.openmanga.common.utils; + +import android.animation.Animator; +import android.support.annotation.NonNull; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +import java.util.WeakHashMap; + +/** + * Created by koitharu on 31.12.17. + */ + +public abstract class AnimationUtils { + + private static final WeakHashMap sAnimations = new WeakHashMap<>(); + + public static void setVisibility(@NonNull View view, int visibility) { + switch (visibility) { + case View.VISIBLE: + Log.d("VVV", "VISIBLE"); + break; + case View.INVISIBLE: + Log.d("VVV", "INVISIBLE"); + break; + case View.GONE: + Log.d("VVV", "GONE"); + break; + default: + Log.d("VVV", String.valueOf(visibility)); + } + final int currentVisibility = view.getVisibility(); + if (visibility == currentVisibility) { + return; + } + Animator oldAnimation = sAnimations.get(view); + if (oldAnimation != null) { + oldAnimation.cancel(); + } + final int duration = view.getResources().getInteger(android.R.integer.config_shortAnimTime); + ViewPropertyAnimator animator = view.animate() + .setDuration(duration); + if (visibility == View.VISIBLE) { + //show it + view.setAlpha(0f); + view.setVisibility(View.VISIBLE); + animator.setListener(new ViewAnimationListener(view, visibility)); + animator.alpha(1f); + } else { + animator.alpha(0f); + animator.setListener(new ViewAnimationListener(view, visibility)); + } + animator.start(); + } + + private static class ViewAnimationListener implements Animator.AnimatorListener { + + private final View mView; + private final int mVisibility; + + ViewAnimationListener(View view, int visibility) { + mView = view; + mVisibility = visibility; + } + + @Override + public void onAnimationStart(Animator animation) { + sAnimations.put(mView, animation); + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mVisibility != View.VISIBLE) { + mView.setVisibility(mVisibility); + mView.setAlpha(1f); + } + sAnimations.remove(mView); + } + + @Override + public void onAnimationCancel(Animator animation) { + sAnimations.remove(mView); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + } + + public static void expand(final View v) { + v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final int targetHeight = v.getMeasuredHeight(); + + v.getLayoutParams().height = 1; + v.setVisibility(View.VISIBLE); + Animation a = new Animation() + { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + v.getLayoutParams().height = interpolatedTime == 1 + ? ViewGroup.LayoutParams.WRAP_CONTENT + : (int)(targetHeight * interpolatedTime); + v.requestLayout(); + } + + @Override + public boolean willChangeBounds() { + return true; + } + }; + + // 1dp/ms + a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density)); + v.startAnimation(a); + } + + public static void collapse(final View v) { + final int initialHeight = v.getMeasuredHeight(); + + Animation a = new Animation() + { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + if(interpolatedTime == 1){ + v.setVisibility(View.GONE); + }else{ + v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime); + v.requestLayout(); + } + } + + @Override + public boolean willChangeBounds() { + return true; + } + }; + + // 1dp/ms + a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density)); + v.startAnimation(a); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/BroadcastUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/BroadcastUtils.java new file mode 100644 index 00000000..f07f722c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/BroadcastUtils.java @@ -0,0 +1,45 @@ +package org.nv95.openmanga.common.utils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.nv95.openmanga.core.models.MangaChapter; + +public abstract class BroadcastUtils { + + public static final String ACTION_DOWNLOAD_DONE = "org.nv95.openmanga.ACTION_DOWNLOAD_DONE"; + + public static void sendDownloadDoneBroadcast(Context context, MangaChapter chapter) { + final Intent intent = new Intent(ACTION_DOWNLOAD_DONE); + intent.putExtra("_chapter", chapter); + context.sendBroadcast(intent); + } + + public static BroadcastReceiver createDownloadsReceiver(DownloadsReceiverCallback callback) { + return new DownloadsReceiver(callback); + } + + private static class DownloadsReceiver extends BroadcastReceiver { + + private final DownloadsReceiverCallback mCallback; + + private DownloadsReceiver(DownloadsReceiverCallback callback) { + mCallback = callback; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null && ACTION_DOWNLOAD_DONE.equals(intent.getAction())) try { + mCallback.onChapterDownloaded(MangaChapter.from(intent.getExtras())); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public interface DownloadsReceiverCallback { + + void onChapterDownloaded(MangaChapter chapter); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/CollectionsUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/CollectionsUtils.java new file mode 100644 index 00000000..2659285a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/CollectionsUtils.java @@ -0,0 +1,190 @@ +package org.nv95.openmanga.common.utils; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; +import android.util.SparseBooleanArray; + +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Created by koitharu on 26.12.17. + */ + +public abstract class CollectionsUtils { + + @Nullable + public static MangaChapter findItemById(Collection collection, long id) { + for (MangaChapter o : collection) { + if (o.id == id) { + return o; + } + } + return null; + } + + public static int findChapterPositionById(List list, long id) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i).id == id) { + return i; + } + } + return -1; + } + + public static int findPagePositionById(ArrayList list, long id) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i).id == id) { + return i; + } + } + return -1; + } + + public static int findCategoryPositionById(List list, long id) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i).id == id) { + return i; + } + } + return -1; + } + + @Nullable + public static T getOrNull(List list, int position) { + try { + return list.get(position); + } catch (Exception e) { + return null; + } + } + + @Nullable + public static T getOrNull(T[] array, int position) { + return position < 0 || position >= array.length ? null : array[position]; + } + + public static T getOrDefault(List list, int position, @Nullable T defaultValue) { + try { + return list.get(position); + } catch (Exception e) { + return defaultValue; + } + } + + public static ArrayList getIfTrue(T[] items, SparseBooleanArray booleanArray) { + final ArrayList values = new ArrayList<>(); + for (int i = 0; i < items.length; i++) { + if (booleanArray.get(i, false)) { + values.add(items[i]); + } + } + return values; + } + + public static boolean contains(@NonNull T[] array, T value) { + for (T o : array) { + if (o != null && o.equals(value)) { + return true; + } + } + return false; + } + + public static String toString(@NonNull Object[] elements, @NonNull String delimiter) { + final StringBuilder builder = new StringBuilder(); + boolean nonFirst = false; + for (Object o: elements) { + if (nonFirst) { + builder.append(delimiter); + } else { + nonFirst = true; + } + builder.append(o.toString()); + } + return builder.toString(); + } + + public static boolean removeByValue(Map map, T value) { + for (Map.Entry o : map.entrySet()) { + if (o.getValue() == value) { + map.remove(o.getKey()); + return true; + } + } + return false; + } + + public static void swap(SparseBooleanArray booleanArray, int x, int p, boolean defaultValue) { + boolean value = booleanArray.get(x, defaultValue); + booleanArray.put(x, booleanArray.get(p, defaultValue)); + booleanArray.put(p, value); + } + + @NonNull + public static int[] convertToInt(@NonNull String[] strings, int defaultValue) { + final int[] res = new int[strings.length]; + for (int i = 0; i < res.length; i++) { + try { + res[i] = Integer.parseInt(strings[i]); + } catch (NumberFormatException e) { + res[i] = defaultValue; + } + } + return res; + } + + public static int indexOf(int[] array, int x) { + for (int i = 0; i < array.length; i++) { + if (array[i] == x) { + return i; + } + } + return -1; + } + + public static boolean containsChapter(List chapters, @NonNull MangaChapter obj) { + for (MangaChapter o : chapters) { + if (o.id == obj.id) { + return true; + } + } + return false; + } + + public static ArrayList mapFirsts(ArrayList> pairs) { + final ArrayList result = new ArrayList<>(pairs.size()); + for (Pair o : pairs) { + result.add(o.first); + } + return result; + } + + public static ArrayList mapSeconds(ArrayList> pairs) { + final ArrayList result = new ArrayList<>(pairs.size()); + for (Pair o : pairs) { + result.add(o.second); + } + return result; + } + + @NonNull + public static ArrayList arrayListOf(T... args) { + final ArrayList list = new ArrayList<>(args.length); + list.addAll(Arrays.asList(args)); + return list; + } + + @NonNull + public static ArrayList empty() { + return new ArrayList<>(0); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/ErrorUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/ErrorUtils.java new file mode 100644 index 00000000..32910b1f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/ErrorUtils.java @@ -0,0 +1,59 @@ +package org.nv95.openmanga.common.utils; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; + +import org.json.JSONException; +import org.nv95.openmanga.BuildConfig; +import org.nv95.openmanga.R; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.SocketTimeoutException; + +/** + * Created by koitharu on 12.01.18. + */ + +public abstract class ErrorUtils { + + @StringRes + public static int getErrorMessage(@Nullable Throwable error) { + if (error == null) { + return R.string.error; + } else if (error instanceof JSONException) { + return R.string.error_bad_response; + } else if (error instanceof SocketTimeoutException) { + return R.string.error_timeout; + } else if (error instanceof IOException) { + return R.string.loading_error; + } else { //TODO + return R.string.error; + } + } + + @NonNull + public static String getErrorMessage(Context context, @Nullable Throwable throwable) { + return context.getString(getErrorMessage(throwable)); + } + + @NonNull + public static String getErrorMessageDetailed(Context context, @Nullable Throwable throwable) { + String message = getErrorMessage(context, throwable); + if (BuildConfig.DEBUG && throwable != null) { + message += ":\n" + throwable.getMessage(); + } + return message; + } + + @NonNull + public static String getStackTrace(@NonNull Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + return sw.toString(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/FilesystemUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/FilesystemUtils.java new file mode 100644 index 00000000..603cbc5a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/FilesystemUtils.java @@ -0,0 +1,100 @@ +package org.nv95.openmanga.common.utils; + +import android.content.Context; +import android.media.MediaScannerConnection; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.webkit.MimeTypeMap; + +import java.io.File; + +/** + * Created by koitharu on 11.01.18. + */ + +public abstract class FilesystemUtils { + + public static long getFileSize(@Nullable File file) { + if (file == null || !file.exists()) { + return 0; + } + if (!file.isDirectory()) { + return file.length(); + } + long size = 0; + final File[] subFiles = file.listFiles(); + for (File o : subFiles) { + if (o.isDirectory()) { + size += getFileSize(o); + } else { + size += o.length(); + } + } + return size; + } + + public static void clearDir(@Nullable File dir) { + if (dir == null || !dir.exists()) { + return; + } + final File[] files = dir.listFiles(); + for (File o : files) { + if (o.isDirectory()) { + deleteDir(o); + } else { + o.delete(); + } + } + } + + public static void deleteDir(@Nullable File dir) { + if (dir == null || !dir.exists()) { + return; + } + final File[] files = dir.listFiles(); + for (File o : files) { + if (o.isDirectory()) { + deleteDir(o); + } else { + o.delete(); + } + } + dir.delete(); + } + + public static void scanFile(@NonNull Context context, @NonNull File file, + @Nullable MediaScannerConnection.OnScanCompletedListener callback) { + MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, callback == null ? + (MediaScannerConnection.OnScanCompletedListener) (path, uri) -> { + + } : callback); + } + + @Nullable + public static File getFile(@Nullable String url) { + return url != null && url.startsWith("file://") ? new File(url.substring(7)) : null; + } + + @Nullable + public static String getExtension(@NonNull String path) { + final int p = path.lastIndexOf('.'); + if (path.length() - p > 6) { + return null; + } else { + return path.substring(p + 1).toLowerCase(); + } + } + + @Nullable + public static String getMimeType(String path) { + final String extension = getExtension(path); + return extension != null ? MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) : null; + } + + @NonNull + public static String getBasename(@NonNull String path) { + final int begin = path.lastIndexOf('/') + 1; + final int end = path.lastIndexOf('.'); + return path.length() - end > 6 ? path.substring(begin) : path.substring(begin, end); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/ImageUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/ImageUtils.java new file mode 100644 index 00000000..15cc537d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/ImageUtils.java @@ -0,0 +1,163 @@ +package org.nv95.openmanga.common.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.ThumbnailUtils; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.widget.ImageView; + +import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.assist.ImageSize; +import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; +import com.nostra13.universalimageloader.core.imageaware.ImageViewAware; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.TransitionDisplayer; +import org.nv95.openmanga.common.utils.network.AppImageDownloader; +import org.nv95.openmanga.core.storage.settings.AppSettings; + +import java.io.File; + +/** + * Created by koitharu on 24.12.17. + */ + +public abstract class ImageUtils { + + private static DisplayImageOptions.Builder sOptionsThumbnail = null; + private static DisplayImageOptions.Builder sOptionsUpdate = null; + + public static void init(Context context) { + sOptionsThumbnail = new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(true) + .resetViewBeforeLoading(true); + + if (!ImageLoader.getInstance().isInited()) { + final int cacheMb = AppSettings.get(context).getCacheMaxSizeMb(); + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) + .defaultDisplayImageOptions(sOptionsThumbnail.build()) + .diskCacheSize(cacheMb * 1024 * 1024) + .imageDownloader(new AppImageDownloader(context)) + .memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // 2 Mb + .build(); + + ImageLoader.getInstance().init(config); + } + Drawable holder = ContextCompat.getDrawable(context, R.drawable.placeholder); + sOptionsThumbnail + .showImageOnFail(holder) + .showImageForEmptyUri(holder) + .showImageOnLoading(holder) + .displayer(new FadeInBitmapDisplayer(500, true, true, false)); + + sOptionsUpdate = new DisplayImageOptions.Builder() + .cacheInMemory(false) + .cacheOnDisk(true) + .resetViewBeforeLoading(false) + .showImageOnLoading(null) + .displayer(new TransitionDisplayer()); + } + + @Nullable + public static Bitmap getCachedImage(String url) { + try { + Bitmap b = ImageLoader.getInstance().getMemoryCache().get(url); + if (b == null) { + File f = ImageLoader.getInstance().getDiskCache().get(url); + if (f != null) { + b = BitmapFactory.decodeFile(f.getPath()); + } + } + return b; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static void setThumbnail(@NonNull ImageView imageView, String url, String referer) { + if (url != null && url.equals(imageView.getTag())) { + return; + } + imageView.setTag(url); + ImageLoader.getInstance().displayImage( + url, + new ImageViewAware(imageView), + sOptionsThumbnail + .extraForDownloader(referer) + .build() + ); + } + + public static void setThumbnail(@NonNull ImageView imageView, @Nullable File file) { + final String url = file == null ? null : "file://" + file.getPath(); + setThumbnail(imageView, url, null); + } + + public static void setThumbnailCropped(@NonNull ImageView imageView, @Nullable String url, @NonNull MetricsUtils.Size size, String referer) { + if (url != null && url.equals(imageView.getTag())) { + return; + } + imageView.setTag(url); + ImageLoader.getInstance().displayImage( + url, + new ImageViewAware(imageView), + sOptionsThumbnail + .extraForDownloader(referer) + .build(), + new ImageSize(size.width, size.height), + null, null + ); + } + + public static void setThumbnailCropped(@NonNull ImageView imageView, @Nullable File file, @NonNull MetricsUtils.Size size) { + final String url = file != null && file.exists() ? "file://" + file.getPath() : null; + setThumbnailCropped(imageView, url, size, null); + } + + public static void setEmptyThumbnail(ImageView imageView) { + ImageLoader.getInstance().cancelDisplayTask(imageView); + imageView.setImageResource(R.drawable.placeholder); + imageView.setTag(null); + } + + public static void recycle(@NonNull ImageView imageView) { + ImageLoader.getInstance().cancelDisplayTask(imageView); + final Drawable drawable = imageView.getDrawable(); + if (drawable != null && drawable instanceof BitmapDrawable) { + //((BitmapDrawable) drawable).getBitmap().recycle(); + imageView.setImageDrawable(null); + } + imageView.setTag(null); + } + + public static void updateImage(@NonNull ImageView imageView, String url, String referer) { + ImageLoader.getInstance().displayImage( + url, + imageView, + sOptionsUpdate + .extraForDownloader(referer) + .build()); + } + + @Nullable + public static Bitmap getThumbnail(String path, int width, int height) { + Bitmap bitmap = getCachedImage(path); + if (bitmap == null && path.startsWith("/")) { + bitmap = BitmapFactory.decodeFile(path); + } + if (bitmap != null) { + bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); + } + return bitmap; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/IntentUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/IntentUtils.java new file mode 100644 index 00000000..55d86470 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/IntentUtils.java @@ -0,0 +1,106 @@ +package org.nv95.openmanga.common.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.support.annotation.NonNull; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.SharedFileProvider; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; +import org.nv95.openmanga.preview.PreviewActivity; +import org.nv95.openmanga.reader.ReaderActivity; + +import java.io.File; +import java.util.List; + +/** + * Created by koitharu on 26.12.17. + */ + +public abstract class IntentUtils { + + public static void shareManga(Context context, MangaHeader mangaHeader) { + final Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, mangaHeader.url); + intent.putExtra(android.content.Intent.EXTRA_SUBJECT, mangaHeader.name); + context.startActivity(Intent.createChooser(intent, context.getString(R.string.share))); + } + + public static void createLauncherShortcutPreview(Context context, MangaHeader mangaHeader) { + final Intent shortcutIntent = new Intent(context, PreviewActivity.class); + shortcutIntent.setAction(PreviewActivity.ACTION_PREVIEW); + shortcutIntent.putExtras(mangaHeader.toBundle()); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + final Intent addIntent = new Intent(); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, mangaHeader.name); + + Bitmap cover = ImageUtils.getCachedImage(mangaHeader.thumbnail); + if (cover == null) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher)); + } else { + final int size = ResourceUtils.getLauncherIconSize(context); + cover = ThumbnailUtils.extractThumbnail(cover, size, size, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, cover); + } + addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + context.getApplicationContext().sendBroadcast(addIntent); + } + + public static void createLauncherShortcutRead(Context context, MangaBookmark bookmark) { + final Intent shortcutIntent = new Intent(context, ReaderActivity.class); + shortcutIntent.setAction(ReaderActivity.ACTION_BOOKMARK_OPEN); + shortcutIntent.putExtras(bookmark.toBundle()); + + final Intent addIntent = new Intent(); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, bookmark.manga.name); + + Bitmap cover = new ThumbnailsStorage(context).get(bookmark); + if (cover == null) { + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.mipmap.ic_launcher)); + } else { + final int size = ResourceUtils.getLauncherIconSize(context); + cover = ThumbnailUtils.extractThumbnail(cover, size, size, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, cover); + } + addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + context.getApplicationContext().sendBroadcast(addIntent); + } + + public static void openBrowser(Context context, String url) { + final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(browserIntent); + } + + public static void shareImage(@NonNull Context context, @NonNull File file) { + Intent i = new Intent(Intent.ACTION_SEND); + i.setType("image/*"); + i.putExtra(Intent.EXTRA_STREAM, SharedFileProvider.getUriForFile(context, SharedFileProvider.AUTHORITY, file)); + context.startActivity(Intent.createChooser(i, context.getString(R.string.share_image))); + } + + public static boolean canResolveBroadcast(@NonNull Context context, @NonNull Intent intent) { + final PackageManager packageManager = context.getPackageManager(); + final List receivers = packageManager.queryBroadcastReceivers(intent, 0); + return receivers != null && receivers.size() > 0; + } + + public static boolean sendBroadcastSafely(@NonNull Context context, @NonNull Intent intent) { + if (canResolveBroadcast(context, intent)) { + context.sendBroadcast(intent); + return true; + } else { + return false; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/LayoutUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/LayoutUtils.java new file mode 100644 index 00000000..74ab9f8c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/LayoutUtils.java @@ -0,0 +1,150 @@ +package org.nv95.openmanga.common.utils; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.OrientationHelper; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.ListView; +import android.widget.TextView; + +/** + * Created by koitharu on 26.12.17. + */ + +public abstract class LayoutUtils { + + public static int getItemCount(RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + return layoutManager == null ? 0 : layoutManager.getItemCount(); + } + + public static int findLastCompletelyVisibleItemPosition(RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + final View child = findOneVisibleChild(layoutManager, layoutManager.getChildCount() - 1, -1, true, false); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + public static int findLastVisibleItemPosition(RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + final View child = findOneVisibleChild(layoutManager, layoutManager.getChildCount() - 1, -1, false, true); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + public static int findFirstVisibleItemPosition(RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + final View child = findOneVisibleChild(layoutManager, 0, layoutManager.getChildCount(), false, true); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + public static int findFirstCompletelyVisibleItemPosition(RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + final View child = findOneVisibleChild(layoutManager, 0, layoutManager.getChildCount(), true, false); + return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child); + } + + private static View findOneVisibleChild(RecyclerView.LayoutManager layoutManager, int fromIndex, int toIndex, + boolean completelyVisible, boolean acceptPartiallyVisible) { + OrientationHelper helper; + if (layoutManager.canScrollVertically()) { + helper = OrientationHelper.createVerticalHelper(layoutManager); + } else { + helper = OrientationHelper.createHorizontalHelper(layoutManager); + } + + final int start = helper.getStartAfterPadding(); + final int end = helper.getEndAfterPadding(); + final int next = toIndex > fromIndex ? 1 : -1; + View partiallyVisible = null; + for (int i = fromIndex; i != toIndex; i += next) { + final View child = layoutManager.getChildAt(i); + final int childStart = helper.getDecoratedStart(child); + final int childEnd = helper.getDecoratedEnd(child); + if (childStart < end && childEnd > start) { + if (completelyVisible) { + if (childStart >= start && childEnd <= end) { + return child; + } else if (acceptPartiallyVisible && partiallyVisible == null) { + partiallyVisible = child; + } + } else { + return child; + } + } + } + return partiallyVisible; + } + + public static void showSoftKeyboard(@NonNull View view) { + view.requestFocus(); + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, 0); + } + } + + public static void hideSoftKeyboard(@NonNull View view) { + InputMethodManager imm = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + public static void setTextOrHide(TextView textView, String text) { + if (android.text.TextUtils.isEmpty(text)) { + textView.setVisibility(View.GONE); + } else { + textView.setText(text); + textView.setVisibility(View.VISIBLE); + } + } + + public static void setSelectionFromTop(RecyclerView recyclerView, int position) { + Log.d("Scroll", "#" + position); + final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager != null) { + if (layoutManager instanceof LinearLayoutManager) { + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, 0); + } else { + layoutManager.scrollToPosition(position); + } + } + } + + public static void scrollToCenter(RecyclerView recyclerView, int position) { + final int firstPos = findFirstVisibleItemPosition(recyclerView); + final int lastPos = findLastVisibleItemPosition(recyclerView); + final int count = getItemCount(recyclerView); + if (position < firstPos) { + setSelectionFromTop(recyclerView, calculateScrollPos(firstPos, position, count)); + } else if (position > lastPos) { + setSelectionFromTop(recyclerView, calculateScrollPos(position, position - lastPos + firstPos, count)); + } + } + + private static int calculateScrollPos(int a, int b, int max) { + return Math.min((a + b) / 2, max); + } + + public static void forceUpdate(@NonNull RecyclerView recyclerView) { + int pos = findFirstCompletelyVisibleItemPosition(recyclerView); + final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + final RecyclerView.Adapter adapter = recyclerView.getAdapter(); + recyclerView.setAdapter(null); + recyclerView.setLayoutManager(null); + recyclerView.setAdapter(adapter); + recyclerView.setLayoutManager(layoutManager); + adapter.notifyDataSetChanged(); + setSelectionFromTop(recyclerView, pos); + } + + public static void checkAll(@NonNull ListView listView) { + final int size = listView.getAdapter().getCount(); + for (int i = 0; i <= size; i++) { + listView.setItemChecked(i, true); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/MenuUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/MenuUtils.java new file mode 100644 index 00000000..2f31908b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/MenuUtils.java @@ -0,0 +1,90 @@ +package org.nv95.openmanga.common.utils; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.support.annotation.IdRes; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.storage.db.CategoriesRepository; +import org.nv95.openmanga.core.storage.db.CategoriesSpecification; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by koitharu on 26.12.17. + */ + +public abstract class MenuUtils { + + public static void buildOpenWithSubmenu(Context context, MangaHeader mangaHeader, MenuItem menuItem) { + final Uri uri = Uri.parse(mangaHeader.url); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + buildIntentSubmenu(context, intent, menuItem); + } + + public static void buildShareSubmenu(Context context, MangaHeader mangaHeader, MenuItem menuItem) { + final Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, mangaHeader.url); + intent.putExtra(android.content.Intent.EXTRA_SUBJECT, mangaHeader.name); + buildIntentSubmenu(context, intent, menuItem); + } + + private static void buildIntentSubmenu(Context context, Intent intent, MenuItem menuItem) { + final SubMenu menu = menuItem.getSubMenu(); + final PackageManager pm = context.getPackageManager(); + final List allActivities = pm.queryIntentActivities(intent, 0); + if (allActivities.isEmpty()) { + menuItem.setVisible(false); + } else { + menuItem.setVisible(true); + menu.clear(); + for (ResolveInfo o : allActivities) { + MenuItem item = menu.add(o.loadLabel(pm)); + item.setIcon(o.loadIcon(pm)); + ComponentName name = new ComponentName(o.activityInfo.applicationInfo.packageName, + o.activityInfo.name); + Intent i = new Intent(intent); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + i.setComponent(name); + item.setIntent(i); + } + } + } + + public static void buildCategoriesSubmenu(Context context, MenuItem menuItem) { + final SubMenu menu = menuItem.getSubMenu(); + final CategoriesRepository categoriesRepository = CategoriesRepository.get(context); + final ArrayList categories = categoriesRepository.query(new CategoriesSpecification().orderByName(false)); + if (categories == null) { + return; + } + if (categories.isEmpty()) { + Category defaultCategory = Category.createDefault(context); + categories.add(defaultCategory); + categoriesRepository.add(defaultCategory); + } + for (Category category : categories) { + MenuItem item = menu.add(R.id.group_categories, category.id, Menu.NONE, category.name); + item.setCheckable(true); + } + } + + public static void setRadioCheckable(MenuItem menuItem, @IdRes int group_id) { + final SubMenu menu = menuItem.getSubMenu(); + if (menu != null) { + menu.setGroupCheckable(group_id, true, true); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/MetricsUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/MetricsUtils.java new file mode 100644 index 00000000..126f80cc --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/MetricsUtils.java @@ -0,0 +1,57 @@ +package org.nv95.openmanga.common.utils; + +import android.content.res.Resources; +import android.support.annotation.DimenRes; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; +import android.view.View; +import android.view.ViewGroup; + +import org.nv95.openmanga.R; + +/** + * Created by koitharu on 29.01.18. + */ + +public abstract class MetricsUtils { + + private static final float DEF_ASPECT_RATIO = 18f / 13f; + + public static Size getPreferredCellSizeMedium(Resources resources) { + return getPreferredCellSize(resources, R.dimen.column_width_medium); + } + + public static Size getPreferredCellSize(Resources resources, @DimenRes int columnWidthDimen) { + int columns = getPreferredColumnsCount(resources, columnWidthDimen); + int width = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (float) columns); + return new Size(width, (int) (width * DEF_ASPECT_RATIO)); + } + + public static int getPreferredColumnsCountMedium(Resources resources) { + return getPreferredColumnsCount(resources, R.dimen.column_width_medium); + } + + public static int getPreferredColumnsCount(Resources resources, @DimenRes int columnWidthDimen) { + int totalWidth = resources.getDisplayMetrics().widthPixels; + int columnWidth = resources.getDimensionPixelSize(columnWidthDimen); + return Math.max(1, Math.round(totalWidth / (float) columnWidth)); + } + + public static class Size { + + public final int width; + public final int height; + + public Size(int width, int height) { + this.width = width; + this.height = height; + } + + @NonNull + @UiThread + public static Size fromLayoutParams(View view) { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + return new Size(layoutParams.width, layoutParams.height); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/PreferencesUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/PreferencesUtils.java new file mode 100644 index 00000000..4bfb0fbc --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/PreferencesUtils.java @@ -0,0 +1,147 @@ +package org.nv95.openmanga.common.utils; + +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.MultiSelectListPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.StringJoinerCompat; +import org.nv95.openmanga.common.views.preferences.ColorPreference; +import org.nv95.openmanga.common.views.preferences.IntegerPreference; + +import java.util.Set; + +/** + * Created by koitharu on 26.12.17. + */ + +public abstract class PreferencesUtils { + + public static void bindPreferenceSummary(@Nullable Preference preference) { + if (preference != null) { + bindPreferenceSummary(preference, preference.getOnPreferenceChangeListener(), null); + } + } + + public static void bindPreferenceSummary(@Nullable Preference preference, + @Nullable Preference.OnPreferenceChangeListener changeListener) { + bindPreferenceSummary(preference, changeListener, null); + } + + public static void bindPreferenceSummary(@Nullable Preference preference, + @Nullable Preference.OnPreferenceChangeListener changeListener, + @Nullable String pattern) { + if (preference == null) { + return; + } + SummaryHandler handler = new SummaryHandler(changeListener, pattern); + handler.initSummary(preference); + preference.setOnPreferenceChangeListener(handler); + + } + + public static void bindSummaryMultiple(@NonNull PreferenceFragment fragment, String... keys) { + for (String key : keys) { + bindPreferenceSummary(fragment.findPreference(key)); + } + } + + public static String buildSummary(@NonNull MultiSelectListPreference preference, @NonNull Set values, + @StringRes int emptySummary) { + return buildSummary(preference, values, emptySummary, 0); + } + + @NonNull + public static String buildSummary(@NonNull MultiSelectListPreference preference, @NonNull Set values, + @StringRes int emptySummary, @StringRes int allSummary) { + if (values.isEmpty()) { + return preference.getContext().getString(emptySummary); + } + final CharSequence[] entries = preference.getEntries(); + final CharSequence[] entryValues = preference.getEntryValues(); + if (allSummary != 0 && entryValues.length == values.size()) { + return preference.getContext().getString(allSummary); + } + final StringJoinerCompat joiner = new StringJoinerCompat(", "); + for (int i = 0; i < entryValues.length; i++) { + if (values.contains(entryValues[i].toString())) { + joiner.add(entries[i]); + } + } + return joiner.toString(); + } + + static class SummaryHandler implements Preference.OnPreferenceChangeListener { + + @Nullable + private final Preference.OnPreferenceChangeListener mChangeListener; + @Nullable + private final String mPattern; + + SummaryHandler(@Nullable Preference.OnPreferenceChangeListener changeListener, @Nullable String pattern) { + mChangeListener = changeListener; + mPattern = pattern; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChangeListener != null && !mChangeListener.onPreferenceChange(preference, newValue)) { + return false; + } + if (preference instanceof ListPreference) { + int index = ((ListPreference) preference).findIndexOfValue((String) newValue); + String summ = ((ListPreference) preference).getEntries()[index].toString(); + preference.setSummary(formatSummary(summ)); + } else if (preference instanceof IntegerPreference) { + preference.setSummary(formatSummary( + String.valueOf(newValue) + )); + } else if (preference instanceof MultiSelectListPreference) { + preference.setSummary(buildSummary((MultiSelectListPreference) preference, + (Set) newValue, R.string.nothing_selected, R.string.all)); + } else if (preference instanceof ColorPreference) { + preference.setSummary(String.format("#%06X", (0xFFFFFF & (int)newValue))); + } else { + preference.setSummary(formatSummary(String.valueOf(newValue))); + } + return true; + } + + void initSummary(Preference preference) { + if (preference instanceof EditTextPreference) { + preference.setSummary(formatSummary(((EditTextPreference) preference).getText())); + } else if (preference instanceof ListPreference) { + preference.setSummary(formatSummary( + ((ListPreference) preference).getEntries()[ + ((ListPreference) preference).findIndexOfValue(((ListPreference) preference).getValue()) + ].toString() + )); + } else if (preference instanceof IntegerPreference) { + preference.setSummary(formatSummary( + String.valueOf(((IntegerPreference) preference).getValue()) + )); + } else if (preference instanceof MultiSelectListPreference) { + preference.setSummary(buildSummary((MultiSelectListPreference) preference, + ((MultiSelectListPreference) preference).getValues(), R.string.nothing_selected, R.string.all)); + } else if (preference instanceof ColorPreference) { + preference.setSummary(String.format("#%06X", (0xFFFFFF & ((ColorPreference) preference).getColor()))); + } else { + preference.setSummary(formatSummary(preference.getSharedPreferences() + .getString(preference.getKey(), null))); + } + } + + private String formatSummary(String value) { + if (mPattern == null) { + return value; + } else { + return String.format(mPattern, value); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/ResourceUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/ResourceUtils.java new file mode 100644 index 00000000..3022227e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/ResourceUtils.java @@ -0,0 +1,134 @@ +package org.nv95.openmanga.common.utils; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.RawRes; +import android.support.annotation.StringRes; +import android.text.format.DateUtils; +import android.util.DisplayMetrics; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.Locale; + +/** + * Created by koitharu on 24.12.17. + */ + +public abstract class ResourceUtils { + + public static String getRawString(Resources resources, @RawRes int resId) { + InputStream is = null; + try { + is = resources.openRawResource(resId); + String myText; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + int i = is.read(); + while (i != -1) { + output.write(i); + i = is.read(); + } + myText = output.toString(); + return myText; + } catch (IOException e) { + return e.getMessage(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static String[] getStringArray(Resources resources, @StringRes int[] stringIds) { + String[] res = new String[stringIds.length]; + for (int i = 0; i < stringIds.length; i++) { + res[i] = resources.getString(stringIds[i]); + } + return res; + } + + public static ArrayList getStringArray(Resources resources, Iterable stringIds) { + Iterator iterator = stringIds.iterator(); + ArrayList stringList = new ArrayList<>(); + while (iterator.hasNext()) { + stringList.add(resources.getString(iterator.next())); + } + return stringList; + } + + public static int dpToPx(Resources resources, float dp) { + float density = resources.getDisplayMetrics().density; + return (int) (dp * density); + } + + public static boolean isTablet(Resources resources) { + return resources.getConfiguration().isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + } + + public static boolean isLandscape(Resources resources) { + return resources.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } + + public static boolean isLandscapeTablet(Resources resources) { + Configuration configuration = resources.getConfiguration(); + return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + } + + public static void setLocale(Resources resources, String locale) { + DisplayMetrics dm = resources.getDisplayMetrics(); + android.content.res.Configuration conf = resources.getConfiguration(); + conf.locale = android.text.TextUtils.isEmpty(locale) ? Locale.getDefault() : new Locale(locale); + resources.updateConfiguration(conf, dm); + } + + /** + * https://stackoverflow.com/questions/18635135/android-shortcut-bitmap-launcher-icon-size/19003905#19003905 + */ + public static int getLauncherIconSize(Context context) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + assert activityManager != null; + int size2 = activityManager.getLauncherLargeIconSize(); + int size1 = (int) context.getResources().getDimension(android.R.dimen.app_icon_size); + return size2 > size1 ? size2 : size1; + } + + @NonNull + public static String formatDateTime(Context context, long time) { + Date date = new Date(time); + DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context.getApplicationContext()); + return dateFormat.format(date); + } + + @NonNull + public static String formatDateTimeRelative(Context context, long time) { + return DateUtils.getRelativeTimeSpanString(context, time).toString(); + } + + @NonNull + public static String formatTimeRelative(long time) { + return DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS).toString(); + } + + public static int colorToArgb(@ColorInt int color) { + return Color.argb( + 255, + Color.red(color), + Color.green(color), + Color.blue(color)); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/TextUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/TextUtils.java new file mode 100644 index 00000000..22b333e0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/TextUtils.java @@ -0,0 +1,78 @@ +package org.nv95.openmanga.common.utils; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.Html; +import android.text.SpannableString; +import android.text.Spanned; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; + +/** + * Created by koitharu on 24.12.17. + */ + +public abstract class TextUtils { + + @NonNull + public static String notNull(@Nullable String str) { + return str == null ? "" : str; + } + + @NonNull + public static String concatIgnoreNulls(String... args) { + final StringBuilder builder = new StringBuilder(); + for (String o : args) { + if (o != null) { + builder.append(o); + } + } + return builder.toString(); + } + + public static Spanned fromHtmlCompat(String html) { + Spanned spanned; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + spanned = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); + } else { + spanned = Html.fromHtml(html); + } + return spanned; + } + + public static Spanned toUpperCase(@NonNull Spanned s) { + Object[] spans = s.getSpans(0, + s.length(), Object.class); + SpannableString spannableString = new SpannableString(s.toString().toUpperCase()); + + // reapply the spans to the now uppercase string + for (Object span : spans) { + spannableString.setSpan(span, + s.getSpanStart(span), + s.getSpanEnd(span), + 0); + } + + return spannableString; + } + + public static String ellipsize(String string, int maxLength) { + return string.length() <= maxLength ? string : string.substring(0, maxLength - 1) + '…'; + } + + public static String inline(String string) { + return string.replaceAll("\\s+", " "); + } + + @NonNull + public static String formatFileSize(long size) { + if(size <= 0) return "0 B"; + final String[] units = new String[] { "B", "kB", "MB", "GB", "TB" }; + int digitGroups = (int) (Math.log10(size)/Math.log10(1024)); + final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(); + symbols.setDecimalSeparator('.'); + symbols.setGroupingSeparator(' '); + return new DecimalFormat("#,##0.#", symbols).format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/ThemeUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/ThemeUtils.java new file mode 100644 index 00000000..66257300 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/ThemeUtils.java @@ -0,0 +1,157 @@ +package org.nv95.openmanga.common.utils; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.support.annotation.AttrRes; +import android.support.annotation.ColorInt; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Px; +import android.support.annotation.StyleRes; +import android.support.v4.content.ContextCompat; +import android.util.TypedValue; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.storage.settings.AppSettings; + +/** + * Created by koitharu on 24.12.17. + */ + +public abstract class ThemeUtils { + + private static final int[] APP_THEMES = new int[]{ + R.style.AppTheme_Default, + R.style.AppTheme_Classic, + R.style.AppTheme_Grey, + R.style.AppTheme_Teal, + R.style.AppTheme_Blue, + R.style.AppTheme_Purple, + R.style.AppTheme_Ambiance, + R.style.AppTheme_Indigo, + R.style.AppThemeDark_Classic, + R.style.AppThemeDark_Blue, + R.style.AppThemeDark_Teal, + R.style.AppThemeDark_Miku, + R.style.AppThemeBlack_Grey, + R.style.AppThemeBlack_Red, + R.style.AppThemeBlack_Black + }; + + public static ColorStateList getAttrColorStateList(Context context, @AttrRes int resId) { + TypedArray a = context.getTheme().obtainStyledAttributes(new int[] {resId}); + int attributeResourceId = a.getResourceId(0, 0); + return ContextCompat.getColorStateList(context, attributeResourceId); + } + + public static Drawable[] getThemedIcons(Context context, @DrawableRes int... resIds) { + boolean dark = isAppThemeDark(context); + PorterDuffColorFilter cf = dark ? + new PorterDuffColorFilter(ContextCompat.getColor(context, R.color.white_overlay_85), PorterDuff.Mode.SRC_ATOP) + : null; + Drawable[] ds = new Drawable[resIds.length]; + for (int i=0;i 7; + } + + public static Drawable getSelectableBackground(Context context) { + return getAttrDrawable(context, R.attr.selectableItemBackground); + } + + public static Drawable getSelectableBackgroundBorderless(Context context) { + return getAttrDrawable(context, R.attr.selectableItemBackgroundBorderless); + } + + @ColorInt + public static int getAccentColor(Context context) { + return getThemeAttrColor(context, R.attr.colorAccent); + } + + public static Drawable getColoredDrawable(@NonNull Context context, @DrawableRes int resId, @AttrRes int colorAttrId) { + final Drawable drawable = ContextCompat.getDrawable(context, resId); + if (drawable != null) { + drawable.setColorFilter( + getThemeAttrColor(context, colorAttrId), + PorterDuff.Mode.SRC_IN + ); + } + return drawable; + } + + @StyleRes + public static int getBottomSheetTheme(Context context) { + return isAppThemeDark(context) ? R.style.AppDialogDark : R.style.AppDialogLight; + } + + @Px + public static int getAttrSizePx(Context context, @AttrRes int resId) { + TypedValue typedValue = new TypedValue(); + TypedArray a = context.obtainStyledAttributes(typedValue.data, new int[] { resId }); + int size = a.getDimensionPixelSize(0, 0); + a.recycle(); + return size; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/network/AppImageDownloader.java b/app/src/main/java/org/nv95/openmanga/common/utils/network/AppImageDownloader.java new file mode 100644 index 00000000..5bcef117 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/network/AppImageDownloader.java @@ -0,0 +1,45 @@ +package org.nv95.openmanga.common.utils.network; + +import android.content.Context; +import android.net.Uri; + +import com.nostra13.universalimageloader.core.download.BaseImageDownloader; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import info.guardianproject.netcipher.NetCipher; + +/** + * Created by koitharu on 24.12.17. + */ + +public class AppImageDownloader extends BaseImageDownloader { + + public AppImageDownloader(Context context) { + super(context); + } + + public AppImageDownloader(Context context, int connectTimeout, int readTimeout) { + super(context, connectTimeout, readTimeout); + } + + @Override + protected HttpURLConnection createConnection(String url, Object extra) throws IOException { + String nurl = url.startsWith("https:") ? "http" + url.substring(5) : url; + nurl = Uri.encode(nurl, ALLOWED_URI_CHARS); + final HttpURLConnection connection = NetCipher.getHttpURLConnection(nurl); + connection.setConnectTimeout(connectTimeout); + connection.setReadTimeout(readTimeout); + final String domain = connection.getURL().getHost(); + final String cookie = CookieStore.getInstance().get(domain); + if (extra != null && extra instanceof String) { + connection.addRequestProperty(NetworkUtils.HEADER_REFERER, (String) extra); + } + if (cookie != null) { + connection.addRequestProperty("cookie", cookie); + } + connection.addRequestProperty(NetworkUtils.HEADER_USER_AGENT, NetworkUtils.USER_AGENT_DEFAULT); + return connection; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/network/CloudflareInterceptor.java b/app/src/main/java/org/nv95/openmanga/common/utils/network/CloudflareInterceptor.java new file mode 100644 index 00000000..ea67670a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/network/CloudflareInterceptor.java @@ -0,0 +1,73 @@ +package org.nv95.openmanga.common.utils.network; + +import com.squareup.duktape.Duktape; + +import java.io.IOException; +import java.util.regex.Pattern; + +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +/** + * from https://github.com/inorichi/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt + */ + +public class CloudflareInterceptor implements Interceptor { + + private static final Pattern OPERATION_PATTERN = Pattern.compile("setTimeout\\(function\\(\\)\\{\\s+(var (?:\\w,)+f.+?\\r?\\n[\\s\\S]+?a\\.value =.+?)\\r?\\n"); + private static final Pattern PASS_PATTERN = Pattern.compile("name=\"pass\" value=\"(.+?)\""); + private static final Pattern CHALLENGE_PATTERN = Pattern.compile("name=\"jschl_vc\" value=\"(\\w+)\""); + + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + if (response.code() == 503 && "cloudflare-nginx".equals(response.header("Server"))) { + try { + return chain.proceed(resolveChallenge(response)); + } catch (Exception e) { + e.printStackTrace(); + } + } + return response; + } + + private Request resolveChallenge(Response response) throws Exception { + Duktape duktape = Duktape.create(); + Request originalRequest = response.request(); + HttpUrl url = originalRequest.url(); + String domain = url.host(); + String content = response.body().string(); + // CloudFlare requires waiting 4 seconds before resolving the challenge + Thread.sleep(4000); + String operation = OPERATION_PATTERN.matcher(content).group(1); + String challenge = CHALLENGE_PATTERN.matcher(content).group(1); + String pass = PASS_PATTERN.matcher(content).group(1); + if (operation == null || challenge == null || pass == null) { + throw new RuntimeException("Failed resolving Cloudflare challenge"); + } + String js = operation + .replaceAll("a\\.value =(.+?) \\+.*", "$1") + .replaceAll("\\s{3,}[a-z](?: = |\\.).+","") + .replaceAll("\n+",""); + + int result = ((Double)duktape.evaluate(js)).intValue(); + String answer = String.valueOf(result) + domain.length(); + String cloudflareUrl = HttpUrl.parse(url.scheme() + "://"+ domain + "/cdn-cgi/l/chk_jschl").newBuilder() + .addQueryParameter("jschl_vc", challenge) + .addQueryParameter("pass", pass) + .addQueryParameter("jschl_answer", answer) + .toString(); + Headers cloudflareHeaders = originalRequest.headers() + .newBuilder() + .add("Referer", url.toString()) + .build(); + + return new Request.Builder() + .url(cloudflareUrl) + .headers(cloudflareHeaders) + .build(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/network/CookieStore.java b/app/src/main/java/org/nv95/openmanga/common/utils/network/CookieStore.java new file mode 100644 index 00000000..1c927674 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/network/CookieStore.java @@ -0,0 +1,69 @@ +package org.nv95.openmanga.common.utils.network; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.ProviderHeader; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.storage.ProvidersStore; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +import okhttp3.Interceptor; +import okhttp3.Response; +/** + * Created by koitharu on 11.01.18. + */ + +public final class CookieStore implements Interceptor { + + @Nullable + private static CookieStore sInstance = null; + + @NonNull + public static CookieStore getInstance() { + if (sInstance == null) { + sInstance = new CookieStore(); + } + return sInstance; + } + + private final HashMap mCookies; + + private CookieStore() { + mCookies = new HashMap<>(); + } + + public void init(@NonNull Context context) { + mCookies.clear(); + final ArrayList providers = new ProvidersStore(context).getUserProviders(); + for (ProviderHeader o : providers) { + final String cookie = MangaProvider.getCookie(context, o.cName); + if (cookie != null) { + mCookies.put(MangaProvider.getDomain(o.cName), cookie); + } + } + } + + @Override + public Response intercept(Chain chain) throws IOException { + final String cookie = mCookies.get(chain.request().url().host()); + if (cookie != null) { + return chain.proceed(chain.request().newBuilder().addHeader("Cookie", cookie).build()); + } else { + return chain.proceed(chain.request()); + } + } + + public void put(String domain, String cookie) { + mCookies.put(domain, cookie); + } + + @Nullable + public String get(String domain) { + return mCookies.get(domain); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/network/HttpException.java b/app/src/main/java/org/nv95/openmanga/common/utils/network/HttpException.java new file mode 100644 index 00000000..cf05398c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/network/HttpException.java @@ -0,0 +1,19 @@ +package org.nv95.openmanga.common.utils.network; + +import java.io.IOException; +/** + * Created by koitharu on 19.01.18. + */ + +public final class HttpException extends IOException { + + private final int mStatusCode; + + public HttpException(int statusCode) { + this.mStatusCode = statusCode; + } + + public int getStatusCode() { + return mStatusCode; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/network/NetworkUtils.java b/app/src/main/java/org/nv95/openmanga/common/utils/network/NetworkUtils.java new file mode 100644 index 00000000..3ff66d7f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/network/NetworkUtils.java @@ -0,0 +1,248 @@ +package org.nv95.openmanga.common.utils.network; + +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import org.json.JSONException; +import org.json.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.nv95.openmanga.sync.RESTResponse; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder; +import info.guardianproject.netcipher.proxy.OrbotHelper; +import okhttp3.CacheControl; +import okhttp3.Headers; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * Created by nv95 on 29.11.16. + */ + +public class NetworkUtils { + + public static final String HEADER_USER_AGENT = "User-Agent"; + public static final String HEADER_REFERER = "Referer"; + + public static final String USER_AGENT_DEFAULT = "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0"; + + private static final CacheControl CACHE_CONTROL_DEFAULT = new CacheControl.Builder().maxAge(10, TimeUnit.MINUTES).build(); + private static final Headers HEADERS_DEFAULT = new Headers.Builder() + .add(HEADER_USER_AGENT, USER_AGENT_DEFAULT) + .build(); + + private static OkHttpClient sHttpClient = null; + + @NonNull + private static OkHttpClient.Builder getClientBuilder() { + return new OkHttpClient.Builder() + /*.connectTimeout(1, TimeUnit.SECONDS) + .readTimeout(1, TimeUnit.SECONDS)*/ + .addInterceptor(CookieStore.getInstance()) + .addInterceptor(new CloudflareInterceptor()); + } + + public static void init(Context context, boolean useTor) { + OkHttpClient.Builder builder = getClientBuilder(); + if (useTor && OrbotHelper.get(context).init()) { + try { + StrongOkHttpClientBuilder.forMaxSecurity(context) + .applyTo(builder, new Intent() + .putExtra(OrbotHelper.EXTRA_STATUS, "ON")); //TODO wtf + } catch (Exception e) { + e.printStackTrace(); + } + } + sHttpClient = builder.build(); + } + + @NonNull + public static String getString(@NonNull String url) throws IOException { + return getString(url, HEADERS_DEFAULT); + } + + @NonNull + public static String getString(@NonNull String url, @NonNull Headers headers) throws IOException { + Request.Builder builder = new Request.Builder() + .url(url) + .headers(headers) + .cacheControl(CACHE_CONTROL_DEFAULT) + .get(); + Response response = null; + try { + response = sHttpClient.newCall(builder.build()).execute(); + ResponseBody body = response.body(); + if (body == null) { + throw new IOException("ResponseBody is null"); + } else { + return body.string(); + } + } finally { + if (response != null) { + response.close(); + } + } + } + + @NonNull + public static String postString(@NonNull String url, String... data) throws IOException { + Request.Builder builder = new Request.Builder() + .url(url) + .cacheControl(CACHE_CONTROL_DEFAULT) + .post(buildFormData(data)); + Response response = null; + try { + response = sHttpClient.newCall(builder.build()).execute(); + ResponseBody body = response.body(); + if (body == null) { + throw new IOException("ResponseBody is null"); + } else { + return body.string(); + } + } finally { + if (response != null) { + response.close(); + } + } + } + + @NonNull + public static JSONObject postJSONObject(@NonNull String url, String... data) throws IOException, JSONException { + return new JSONObject(postString(url, data)); + } + + @NonNull + public static Document postDocument(@NonNull String url, String... data) throws IOException { + return Jsoup.parse(postString(url, data), url); + } + + @NonNull + public static Document getDocument(@NonNull String url) throws IOException { + return getDocument(url, HEADERS_DEFAULT); + } + + @NonNull + public static Document getDocument(@NonNull String url, @NonNull Headers headers) throws IOException { + return Jsoup.parse(getString(url, headers), url); + } + + @NonNull + public static JSONObject getJSONObject(@NonNull String url) throws IOException, JSONException { + return new JSONObject(getString(url)); + } + + + @NonNull + public static OkHttpClient getHttpClient() { + return sHttpClient != null ? sHttpClient : getClientBuilder().build(); + } + + public static int getContentLength(Response response) { + String header = response.header("content-length"); + if (header == null) { + return -1; + } else { + try { + return Integer.parseInt(header); + } catch (NumberFormatException e) { + e.printStackTrace(); + return -1; + } + } + } + + @Nullable + public static String authorize(@NonNull String url, String... data) throws IOException { + Request.Builder builder = new Request.Builder() + .url(url) + .cacheControl(CACHE_CONTROL_DEFAULT) + .post(buildFormData(data)); + Response response = null; + try { + response = sHttpClient.newCall(builder.build()).execute(); + return TextUtils.join("; ", response.headers("set-cookie")); + } finally { + if (response != null) { + response.close(); + } + } + } + + private static RequestBody buildFormData(@NonNull String[] data) { + final MultipartBody.Builder builder = new MultipartBody.Builder(); + builder.setType(MultipartBody.FORM); + for (int i = 0; i < data.length; i = i + 2) { + builder.addFormDataPart(data[i], data[i+1]); + } + return builder.build(); + } + + @NonNull + public static RESTResponse restQuery(String url, @Nullable String token, String method, String... data) { + Response response = null; + try { + Request.Builder builder = new Request.Builder() + .url(url) + .cacheControl(CACHE_CONTROL_DEFAULT) + .method(method, buildFormData(data)) + .post(buildFormData(data)); + if (!android.text.TextUtils.isEmpty(token)) { + builder.header("X-AuthToken", token); + } + response = sHttpClient.newCall(builder.build()).execute(); + ResponseBody body = response.body(); + if (body != null) { + return new RESTResponse(new JSONObject(body.string()), response.code()); + } else { + return new RESTResponse(new JSONObject(), response.code()); + } + } catch (Exception e) { + e.printStackTrace(); + return RESTResponse.fromThrowable(e); + } finally { + if (response != null) { + response.close(); + } + } + } + + public static boolean isNetworkAvailable(Context context) { + return isNetworkAvailable(context, true); + } + + public static boolean isNetworkAvailable(Context context, boolean allowMetered) { + final ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (manager == null) { + return false; + } + final NetworkInfo network = manager.getActiveNetworkInfo(); + return network != null && network.isConnected() && (allowMetered || isNotMetered(network)); + } + + private static boolean isNotMetered(NetworkInfo networkInfo) { + if(networkInfo.isRoaming()) return false; + final int type = networkInfo.getType(); + return type == ConnectivityManager.TYPE_WIFI + || type == ConnectivityManager.TYPE_WIMAX + || type == ConnectivityManager.TYPE_ETHERNET; + } + + @NonNull + public static String getDomainWithScheme(@NonNull String url) { + int p = url.indexOf('/', 10); + return url.substring(0, p); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/utils/network/UrlQueryBuilder.java b/app/src/main/java/org/nv95/openmanga/common/utils/network/UrlQueryBuilder.java new file mode 100644 index 00000000..d7db3bb7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/utils/network/UrlQueryBuilder.java @@ -0,0 +1,51 @@ +package org.nv95.openmanga.common.utils.network; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public final class UrlQueryBuilder { + + private final String mPath; + private final HashMap mParams; + + public UrlQueryBuilder(String path) { + mPath = path; + mParams = new HashMap<>(); + } + + @NonNull + @Override + public String toString() { + if (mParams.isEmpty()) { + return mPath; + } else { + final StringBuilder builder = new StringBuilder(mPath).append("?"); + for (Map.Entry o : mParams.entrySet()) { + builder.append(o.getKey()) + .append("=") + .append(o.getValue()) + .append("&"); + } + builder.setLength(builder.length() - 1); + return builder.toString(); + } + } + + public UrlQueryBuilder put(String param, String value) { + mParams.put(param, value); + return this; + } + + public UrlQueryBuilder put(String param, int value) { + mParams.put(param, String.valueOf(value)); + return this; + } + + public UrlQueryBuilder put(String param, Object value) { + mParams.put(param, value.toString()); + return this; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/AspectRatioImageView.java b/app/src/main/java/org/nv95/openmanga/common/views/AspectRatioImageView.java new file mode 100644 index 00000000..ca0c09b2 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/AspectRatioImageView.java @@ -0,0 +1,43 @@ +package org.nv95.openmanga.common.views; + +import android.content.Context; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +/** + * Created by koitharu on 24.12.17. + */ + +public class AspectRatioImageView extends AppCompatImageView { + + protected double mAspectRatio = 18f / 13f; + + public AspectRatioImageView(Context context) { + super(context); + } + + public AspectRatioImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AspectRatioImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setAspectRatio(float value) { + mAspectRatio = value; + } + + public void setAspectRatio(int height, int width) { + mAspectRatio = height / (float)width; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int originalWidth = MeasureSpec.getSize(widthMeasureSpec); + int calculatedHeight = (int) (originalWidth * mAspectRatio); + super.onMeasure( + MeasureSpec.makeMeasureSpec(originalWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(calculatedHeight, MeasureSpec.EXACTLY) + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/BorderlessButton.java b/app/src/main/java/org/nv95/openmanga/common/views/BorderlessButton.java new file mode 100644 index 00000000..a9f0e6a0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/BorderlessButton.java @@ -0,0 +1,27 @@ +package org.nv95.openmanga.common.views; + +import android.content.Context; +import android.support.v7.widget.AppCompatButton; +import android.util.AttributeSet; + +import org.nv95.openmanga.common.utils.ThemeUtils; + +/** + * Created by koitharu on 11.01.18. + */ + +public final class BorderlessButton extends AppCompatButton { + + public BorderlessButton(Context context) { + this(context, null, 0); + } + + public BorderlessButton(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BorderlessButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setBackgroundDrawable(ThemeUtils.getSelectableBackgroundBorderless(context)); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/EndlessRecyclerView.java b/app/src/main/java/org/nv95/openmanga/common/views/EndlessRecyclerView.java new file mode 100644 index 00000000..3754eb79 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/EndlessRecyclerView.java @@ -0,0 +1,97 @@ +package org.nv95.openmanga.common.views; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + +import org.nv95.openmanga.common.utils.LayoutUtils; + +/** + * Created by koitharu on 07.01.18. + */ + +public final class EndlessRecyclerView extends RecyclerView { + + private final EndlessScrollHelper mEndlessScrollHelper; + @Nullable + private OnLoadMoreListener mOnLoadMoreListener = null; + private boolean mLoadingEnabled = false; + + public EndlessRecyclerView(Context context) { + this(context, null, 0); + } + + public EndlessRecyclerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public EndlessRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mEndlessScrollHelper = new EndlessScrollHelper(); + this.addOnScrollListener(mEndlessScrollHelper); + } + + @Override + public void setAdapter(Adapter adapter) { + super.setAdapter(adapter); + if (adapter != null && adapter instanceof EndlessAdapter) { + mLoadingEnabled = true; + mEndlessScrollHelper.mLoading = false; + ((EndlessAdapter) adapter).setHasNext(true); + } + } + + public void setOnLoadMoreListener(@Nullable OnLoadMoreListener onLoadMoreListener) { + mOnLoadMoreListener = onLoadMoreListener; + } + + public void onLoadingStarted() { + mEndlessScrollHelper.mLoading = true; + final Adapter adapter = getAdapter(); + if (adapter != null && adapter instanceof EndlessAdapter) { + ((EndlessAdapter) adapter).setHasNext(true); + } + } + + public void onLoadingFinished(boolean enableNext) { + mLoadingEnabled = enableNext; + mEndlessScrollHelper.mLoading = false; + final Adapter adapter = getAdapter(); + if (adapter != null && adapter instanceof EndlessAdapter) { + ((EndlessAdapter) adapter).setHasNext(enableNext); + } + mEndlessScrollHelper.onScrolled(this, 0, 0); + } + + private class EndlessScrollHelper extends OnScrollListener { + + private static final int VISIBLE_THRESHOLD = 2; + + private int mLastVisibleItem, mTotalItemCount; + private boolean mLoading = false; + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + mTotalItemCount = LayoutUtils.getItemCount(recyclerView); + mLastVisibleItem = LayoutUtils.findLastVisibleItemPosition(recyclerView); + if (!mLoading && mLoadingEnabled && mTotalItemCount <= (mLastVisibleItem + VISIBLE_THRESHOLD)) { + // End has been reached + // Do something + if (mOnLoadMoreListener != null) { + mLoading = mOnLoadMoreListener.onLoadMore(); + } + } + } + } + + public interface OnLoadMoreListener { + + boolean onLoadMore(); + } + + public interface EndlessAdapter { + void setHasNext(boolean hasNext); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/ExpansionPanel.java b/app/src/main/java/org/nv95/openmanga/common/views/ExpansionPanel.java new file mode 100644 index 00000000..e232b21d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/ExpansionPanel.java @@ -0,0 +1,99 @@ +package org.nv95.openmanga.common.views; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v4.widget.TextViewCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.AnimationUtils; + +/** + * Created by koitharu on 02.02.18. + */ + +public final class ExpansionPanel extends LinearLayout { + + private static final String TAG_PERSISTENT = "persistent"; + + private final TextView mTextViewControl; + private final View mDivider; + private boolean mExpanded = false; + + public ExpansionPanel(Context context) { + this(context, null, 0); + } + + public ExpansionPanel(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public ExpansionPanel(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + View.inflate(context, R.layout.view_expansion_panel, this); + setOrientation(VERTICAL); + mTextViewControl = findViewById(R.id.textView_control); + mDivider = findViewById(R.id.divider); + mTextViewControl.setOnClickListener(v -> toggle()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + reorderViews(); + } + + public void toggle() { + setExpanded(!mExpanded); + } + + public void setExpanded(boolean expanded) { + if (expanded == mExpanded) { + return; + } + mExpanded = expanded; + if (mExpanded) { + expand(); + } else { + collapse(); + } + } + + private void expand() { + mExpanded = true; + mTextViewControl.setText(R.string.hide_details); + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mTextViewControl, + 0, 0, R.drawable.ic_collapse_black, 0); + AnimationUtils.expand(getChildAt(0)); + mDivider.setVisibility(VISIBLE); + } + + private void collapse() { + mExpanded = false; + mTextViewControl.setText(R.string.show_more_details); + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mTextViewControl, + 0, 0, R.drawable.ic_expand_black, 0); + AnimationUtils.collapse(getChildAt(0)); + mDivider.setVisibility(GONE); + } + + private void reorderViews() { + removeView(mDivider); + removeView(mTextViewControl); + addView(mDivider); + addView(mTextViewControl); + } + + public static void hideChild(View view) { + view.setTag(TAG_PERSISTENT); + view.setVisibility(GONE); + } + + public static void showChild(View view) { + view.setTag(null); + view.setVisibility(VISIBLE); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/RatingView.java b/app/src/main/java/org/nv95/openmanga/common/views/RatingView.java new file mode 100644 index 00000000..ab3f17e2 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/RatingView.java @@ -0,0 +1,70 @@ +package org.nv95.openmanga.common.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatTextView; +import android.util.AttributeSet; +import android.view.View; + +import org.nv95.openmanga.R; + +/** + * Created by koitharu on 26.12.17. + */ + +public class RatingView extends AppCompatTextView { + + private final Drawable mStarDrawable; + private static int[] sColors = null; + + public RatingView(Context context) { + this(context, null, 0); + } + + public RatingView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RatingView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + if (sColors == null) { + sColors = new int[] { + ContextCompat.getColor(context, R.color.rating_1), + ContextCompat.getColor(context, R.color.rating_2), + ContextCompat.getColor(context, R.color.rating_3), + ContextCompat.getColor(context, R.color.rating_4), + ContextCompat.getColor(context, R.color.rating_5) + }; + } + setLines(1); + setSingleLine(true); + mStarDrawable = ContextCompat.getDrawable(context, R.drawable.ic_star); + setCompoundDrawablesWithIntrinsicBounds(mStarDrawable, null, null, null); + } + + @SuppressLint("SetTextI18n") + public void setRating(short value) { + if (value == 0) { + setVisibility(View.INVISIBLE); + } else { + setVisibility(View.VISIBLE); + } + setText(value + "%"); + int color; + if (value >= 90) { + color = sColors[4]; + } else if (value >= 85) { + color = sColors[3]; + } else if (value >= 60) { + color = sColors[2]; + } else if (value >= 45) { + color = sColors[1]; + } else { + color = sColors[0]; + } + mStarDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/StaggeredGridLayout.java b/app/src/main/java/org/nv95/openmanga/common/views/StaggeredGridLayout.java new file mode 100644 index 00000000..526964cc --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/StaggeredGridLayout.java @@ -0,0 +1,97 @@ +package org.nv95.openmanga.common.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import java.util.Arrays; + +public class StaggeredGridLayout extends ViewGroup { + + private int mColumns = 2; + private float mColumnWidth = 0; + private int mLayoutHeight = 0; + private int[] mColumnsHeights = new int[mColumns]; + + public StaggeredGridLayout(Context context) { + this(context, null, 0); + } + + public StaggeredGridLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public StaggeredGridLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setColumnsCount(int columnsCount) { + mColumns = columnsCount; + mColumnsHeights = new int[mColumns]; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Arrays.fill(mColumnsHeights, 0); + // Total layout width + int layoutWidth = MeasureSpec.getSize(widthMeasureSpec); + // Usable layout width for children once padding is removed + int layoutUsableWidth = layoutWidth - getPaddingLeft() - getPaddingRight(); + if (layoutUsableWidth < 0) + layoutUsableWidth = 0; + + // Total layout height + mLayoutHeight = MeasureSpec.getSize(heightMeasureSpec); + + // Calculate width assigned to each column: the usable width divided by + // the number of columns, minus horizontal spacing + mColumnWidth = layoutUsableWidth / mColumns + - ((getPaddingLeft() * (mColumns - 1)) / mColumns); + + // Measure each children + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + // force the width of the children to be the width previously + // calculated for columns... + int childWidthSpec = MeasureSpec.makeMeasureSpec( + (int) mColumnWidth, MeasureSpec.EXACTLY); + // ... but let them grow vertically + int childHeightSpec = MeasureSpec.makeMeasureSpec(0, + MeasureSpec.UNSPECIFIED); + child.measure(childWidthSpec, childHeightSpec); + } + + // Get the final total height of the layout. It will be that of the + // higher column once all chidren are in place. Every child is added to + // the sortest column at the moment of addition + for (int i = 0; i < getChildCount(); i++) { + int column = i % mColumns; + mColumnsHeights[column] += getChildAt(i).getMeasuredHeight() + getPaddingTop(); + } + int mFinalHeight = mColumnsHeights[0]; + for (int i = 0;i < mColumns; i++) { + if (mFinalHeight < mColumnsHeights[i]) { + mFinalHeight = mColumnsHeights[i]; + } + } + + setMeasuredDimension(layoutWidth, mFinalHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + Arrays.fill(mColumnsHeights, 0); + for (int i = 0; i < getChildCount(); i++) { + final View view = getChildAt(i); + int column = i % mColumns; + int left = getPaddingLeft() + l + (int) (mColumnWidth * column) + + (getPaddingLeft() * column); + view.layout(left, mColumnsHeights[column] + getPaddingTop(), + left + view.getMeasuredWidth(), mColumnsHeights[column] + + view.getMeasuredHeight() + getPaddingTop()); + mColumnsHeights[column] = mColumnsHeights[column] + + view.getMeasuredHeight() + getPaddingTop(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/TextProgressView.java b/app/src/main/java/org/nv95/openmanga/common/views/TextProgressView.java new file mode 100644 index 00000000..f6e7cb1d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/TextProgressView.java @@ -0,0 +1,156 @@ +package org.nv95.openmanga.common.views; + +import android.animation.LayoutTransition; +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ResourceUtils; + +/** + * Created by koitharu on 09.01.18. + */ + +public final class TextProgressView extends LinearLayout { + + private static final int MIN_SHOW_TIME = 500; // ms + private static final int MIN_DELAY = 500; // ms + public static final int INDETERMINATE = -1; + + private long mStartTime = -1; + + private boolean mPostedHide = false; + private boolean mPostedShow = false; + private boolean mDismissed = false; + + private final Runnable mDelayedHide = () -> { + mPostedHide = false; + mStartTime = -1; + setVisibility(View.GONE); + }; + + private final Runnable mDelayedShow = () -> { + mPostedShow = false; + if (!mDismissed) { + mStartTime = System.currentTimeMillis(); + setVisibility(View.VISIBLE); + } + }; + + private final ProgressBar mProgressBar; + private final TextView mTextView; + + private int mMax = 0; + private int mProgress = -1; + + public TextProgressView(Context context) { + this(context, null, 0); + } + + public TextProgressView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + View.inflate(context, R.layout.view_progress, this); + setOrientation(VERTICAL); + setLayoutTransition(new LayoutTransition()); + setGravity(Gravity.CENTER_HORIZONTAL); + final int padding = ResourceUtils.dpToPx(context.getResources(),4); + setPadding(padding, padding, padding, padding); + mTextView = findViewById(android.R.id.text1); + mProgressBar = findViewById(android.R.id.progress); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + removeCallbacks(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(); + } + + private void removeCallbacks() { + removeCallbacks(mDelayedHide); + removeCallbacks(mDelayedShow); + } + + /** + * Hide the progress view if it is visible. The progress view will not be + * hidden until it has been shown for at least a minimum show time. If the + * progress view was not yet visible, cancels showing the progress view. + */ + public void hide() { + mDismissed = true; + removeCallbacks(mDelayedShow); + long diff = System.currentTimeMillis() - mStartTime; + if (diff >= MIN_SHOW_TIME || mStartTime == -1) { + // The progress spinner has been shown long enough + // OR was not shown yet. If it wasn't shown yet, + // it will just never be shown. + setVisibility(View.GONE); + } else { + // The progress spinner is shown, but not long enough, + // so put a delayed message in to hide it when its been + // shown long enough. + if (!mPostedHide) { + postDelayed(mDelayedHide, MIN_SHOW_TIME - diff); + mPostedHide = true; + } + } + } + + /** + * Show the progress view after waiting for a minimum delay. If + * during that time, hide() is called, the view is never made visible. + */ + public void show() { + // Reset the start time. + mStartTime = -1; + mDismissed = false; + removeCallbacks(mDelayedHide); + if (!mPostedShow) { + postDelayed(mDelayedShow, MIN_DELAY); + mPostedShow = true; + } + } + + public void setMax(int max) { + mMax = max; + updateState(); + } + + public void setProgress(int progress) { + mProgress = progress; + updateState(); + } + + public void setProgress(int progress, int max) { + mProgress = progress; + mMax = max; + updateState(); + } + + @SuppressLint("SetTextI18n") + private void updateState() { + if (mMax == 0 || mProgress < 0) { + mTextView.setVisibility(GONE); + } else { + mTextView.setVisibility(VISIBLE); + final int percent = Math.round(mProgress / mMax * 100); + mTextView.setText(percent + "%"); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/TintTextView.java b/app/src/main/java/org/nv95/openmanga/common/views/TintTextView.java new file mode 100644 index 00000000..981a6425 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/TintTextView.java @@ -0,0 +1,62 @@ +package org.nv95.openmanga.common.views; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.widget.AppCompatTextView; +import android.util.AttributeSet; + +/** + * Created by koitharu on 02.02.18. + */ + +public final class TintTextView extends AppCompatTextView { + + public TintTextView(Context context) { + super(context); + } + + public TintTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public TintTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { + repaint(left); + repaint(right); + repaint(bottom); + repaint(top); + super.setCompoundDrawables(left, top, right, bottom); + } + + @Override + public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { + repaint(start); + repaint(end); + repaint(bottom); + repaint(top); + super.setCompoundDrawablesRelative(start, top, end, bottom); + } + + private void repaint(@Nullable Drawable drawable) { + if (drawable != null) { + DrawableCompat.setTintList(drawable, getTextColors()); + DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_IN); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + final Drawable[] drawables = getCompoundDrawables(); + for (Drawable o : drawables) { + repaint(o); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/TwoPaneLayout.java b/app/src/main/java/org/nv95/openmanga/common/views/TwoPaneLayout.java new file mode 100644 index 00000000..97dfe8ad --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/TwoPaneLayout.java @@ -0,0 +1,55 @@ +package org.nv95.openmanga.common.views; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import org.nv95.openmanga.BuildConfig; + +/** + * Created by koitharu on 11.01.18. + */ + +public final class TwoPaneLayout extends ViewGroup { + + protected double mAspectRatio = 13f / 18f; + + public TwoPaneLayout(Context context) { + this(context, null, 0); + } + + public TwoPaneLayout(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public TwoPaneLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (BuildConfig.DEBUG && getChildCount() != 2) { + throw new IllegalStateException("child count must be 2"); + } + final View firstChild = getChildAt(0); + final View secondChild = getChildAt(1); + + int childLeft = this.getPaddingLeft(); + int childTop = this.getPaddingTop(); + int childRight = this.getMeasuredWidth() - this.getPaddingRight(); + int childBottom = this.getMeasuredHeight() - this.getPaddingBottom(); + int childHeight = childBottom - childTop; + + int firstWidth = (int) (childHeight * mAspectRatio); + + firstChild.measure(MeasureSpec.makeMeasureSpec(firstWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); + firstChild.layout(childLeft, childTop, childLeft + firstWidth, childBottom); + + secondChild.measure(MeasureSpec.makeMeasureSpec(childRight - firstWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST)); + secondChild.layout(childLeft + firstWidth, childTop, childRight, childBottom); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/behavior/BottomNavigationBehavior.java b/app/src/main/java/org/nv95/openmanga/common/views/behavior/BottomNavigationBehavior.java new file mode 100644 index 00000000..40a276fd --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/behavior/BottomNavigationBehavior.java @@ -0,0 +1,221 @@ +package org.nv95.openmanga.common.views.behavior; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; +/** + * Created by koitharu on 24.12.17. + */ + +public final class BottomNavigationBehavior extends VerticalScrollingBehavior { + private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator(); + private final BottomNavigationWithSnackbar mWithSnackBarImpl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? new LollipopBottomNavWithSnackBarImpl() : new PreLollipopBottomNavWithSnackBarImpl(); + private int mTabLayoutId; + private boolean hidden = false; + private ViewPropertyAnimatorCompat mOffsetValueAnimator; + private ViewGroup mTabLayout; + private int mSnackbarHeight = -1; + private boolean scrollingEnabled = true; + private boolean hideAlongSnackbar = false; + int[] attrsArray = new int[]{ + android.R.attr.id, android.R.attr.elevation}; + private int mElevation = 8; + + public BottomNavigationBehavior() { + super(); + } + + public BottomNavigationBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, + attrsArray); + mTabLayoutId = a.getResourceId(0, View.NO_ID); + mElevation = a.getResourceId(1, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mElevation, context.getResources().getDisplayMetrics())); + a.recycle(); + } + + public static BottomNavigationBehavior from(@NonNull V view) { + ViewGroup.LayoutParams params = view.getLayoutParams(); + if (!(params instanceof CoordinatorLayout.LayoutParams)) { + throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); + } + CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params) + .getBehavior(); + if (!(behavior instanceof BottomNavigationBehavior)) { + throw new IllegalArgumentException( + "The view is not associated with BottomNavigationBehavior"); + } + return (BottomNavigationBehavior) behavior; + } + + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { + mWithSnackBarImpl.updateSnackbar(parent, dependency, child); + return dependency instanceof Snackbar.SnackbarLayout; + } + + @Override + public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) { + updateScrollingForSnackbar(dependency, child, true); + super.onDependentViewRemoved(parent, child, dependency); + } + + private void updateScrollingForSnackbar(View dependency, V child, boolean enabled) { + if (dependency instanceof Snackbar.SnackbarLayout) { + scrollingEnabled = enabled; + if (!hideAlongSnackbar && ViewCompat.getTranslationY(child) != 0) { + ViewCompat.setTranslationY(child, 0); + hidden = false; + hideAlongSnackbar = true; + } else if (hideAlongSnackbar) { + hidden = true; + animateOffset(child, -child.getHeight()); + } + } + } + + @Override + public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { + updateScrollingForSnackbar(dependency, child, false); + return super.onDependentViewChanged(parent, child, dependency); + } + + @Override + public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { + boolean layoutChild = super.onLayoutChild(parent, child, layoutDirection); + if (mTabLayout == null && mTabLayoutId != View.NO_ID) { + mTabLayout = findTabLayout(child); + elevateNavigationView(); + } + + return layoutChild; + } + + @Nullable + private ViewGroup findTabLayout(@NonNull View child) { + if (mTabLayoutId == 0) return null; + return (ViewGroup) child.findViewById(mTabLayoutId); + } + + @Override + public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) { + } + + @Override + public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) { + handleDirection(child, scrollDirection); + } + + private void handleDirection(V child, @ScrollDirection int scrollDirection) { + if (!scrollingEnabled) return; + if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) { + hidden = false; + animateOffset(child, 0); + } else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) { + hidden = true; + animateOffset(child, child.getHeight()); + } + } + + @Override + protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) { + handleDirection(child, scrollDirection); + return true; + } + + private void animateOffset(final V child, final int offset) { + ensureOrCancelAnimator(child); + mOffsetValueAnimator.translationY(offset).start(); + } + + + + private void ensureOrCancelAnimator(@NonNull V child) { + if (mOffsetValueAnimator == null) { + mOffsetValueAnimator = ViewCompat.animate(child); + mOffsetValueAnimator.setDuration(100); + mOffsetValueAnimator.setInterpolator(INTERPOLATOR); + } else { + mOffsetValueAnimator.cancel(); + } + } + + private void elevateNavigationView() { + if (mTabLayout != null) { + ViewCompat.setElevation(mTabLayout, mElevation); + } + } + + public boolean isScrollingEnabled() { + return scrollingEnabled; + } + + public void setScrollingEnabled(boolean scrollingEnabled) { + this.scrollingEnabled = scrollingEnabled; + } + + public void setHidden(V view, boolean bottomLayoutHidden) { + if (!bottomLayoutHidden && hidden) { + animateOffset(view, 0); + } else if (bottomLayoutHidden && !hidden) { + animateOffset(view, -view.getHeight()); + } + hidden = bottomLayoutHidden; + } + + private interface BottomNavigationWithSnackbar { + void updateSnackbar(CoordinatorLayout parent, View dependency, View child); + } + + private class PreLollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar { + + @Override + public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) { + if (dependency instanceof Snackbar.SnackbarLayout) { + if (mSnackbarHeight == -1) { + mSnackbarHeight = dependency.getHeight(); + } + + int targetPadding = child.getMeasuredHeight(); + + int shadow = (int) ViewCompat.getElevation(child); + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams(); + layoutParams.bottomMargin = targetPadding - shadow; + child.bringToFront(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + child.getParent().requestLayout(); + ((View) child.getParent()).invalidate(); + } + } + } + } + + private class LollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar { + + @Override + public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) { + if (dependency instanceof Snackbar.SnackbarLayout) { + if (mSnackbarHeight == -1) { + mSnackbarHeight = dependency.getHeight(); + } + int targetPadding = (mSnackbarHeight + + child.getMeasuredHeight()); + dependency.setPadding(dependency.getPaddingLeft(), + dependency.getPaddingTop(), dependency.getPaddingRight(), targetPadding + ); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/behavior/CenterGravityBehavior.java b/app/src/main/java/org/nv95/openmanga/common/views/behavior/CenterGravityBehavior.java new file mode 100644 index 00000000..45c4c72c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/behavior/CenterGravityBehavior.java @@ -0,0 +1,69 @@ +package org.nv95.openmanga.common.views.behavior; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.View; +/** + * Created by koitharu on 26.12.17. + */ + +public final class CenterGravityBehavior extends CoordinatorLayout.Behavior { + + private int scrollY = 0; + private int maxScroll = 0; + + public CenterGravityBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, + @NonNull View directTargetChild, @NonNull View target, int nestedScrollAxes) { + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; + } + + @Override + public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { + handleScroll(parent, child, 0); + return super.onDependentViewChanged(parent, child, dependency); + } + + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { + return dependency instanceof AppBarLayout; + } + + @Override + public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, + @NonNull View target, int dx, int dy, @NonNull int[] consumed) { + handleScroll(coordinatorLayout, child, dy); + } + + private void handleScroll(CoordinatorLayout coordinatorLayout, View child, int dy) { + if (maxScroll == 0) { + for (int i = 0; i < coordinatorLayout.getChildCount(); i++) { + View o = coordinatorLayout.getChildAt(i); + if (o instanceof AppBarLayout) { + maxScroll = ((AppBarLayout) o).getTotalScrollRange(); + break; + } + } + } + if (maxScroll == 0) { + return; + } + + scrollY += dy; + if (scrollY > maxScroll) { + scrollY = maxScroll; + } else if (scrollY < 0) { + scrollY = 0; + } + int offset = (scrollY - maxScroll) / 2; + child.setTranslationY(offset); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/behavior/ScrollAwareFABBehavior.java b/app/src/main/java/org/nv95/openmanga/common/views/behavior/ScrollAwareFABBehavior.java new file mode 100644 index 00000000..b6fd418a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/behavior/ScrollAwareFABBehavior.java @@ -0,0 +1,77 @@ +package org.nv95.openmanga.common.views.behavior; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v4.view.animation.FastOutSlowInInterpolator; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.Interpolator; + +/** + * Created by koitharu on 31.12.17. + */ + +public final class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { + + private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); + + private boolean mIsAnimatingOut = false; + private boolean mShowing = true; + + public ScrollAwareFABBehavior() { + super(); + } + + public ScrollAwareFABBehavior(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + @Override + public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); + if (dyConsumed > 0 && !this.mIsAnimatingOut && mShowing) { + animateOut(child); + } else if (dyConsumed < 0 && !mShowing) { + animateIn(child); + } + } + + + + @Override + public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) { + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; + } + + private void animateOut(final FloatingActionButton button) { + ViewCompat.animate(button) + .translationY(button.getMeasuredHeight() + + ((CoordinatorLayout.LayoutParams) button.getLayoutParams()).bottomMargin) + .setInterpolator(INTERPOLATOR).withLayer() + .setListener(new ViewPropertyAnimatorListener() { + public void onAnimationStart(View view) { + mIsAnimatingOut = true; + } + + public void onAnimationCancel(View view) { + mIsAnimatingOut = false; + } + + public void onAnimationEnd(View view) { + mIsAnimatingOut = false; + //view.setVisibility(View.INVISIBLE); + mShowing = false; + } + }).start(); + } + + private void animateIn(FloatingActionButton button) { + mShowing = true; + ViewCompat.animate(button).translationY(0) + .setInterpolator(INTERPOLATOR).withLayer().setListener(null) + .start(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/behavior/VerticalScrollingBehavior.java b/app/src/main/java/org/nv95/openmanga/common/views/behavior/VerticalScrollingBehavior.java new file mode 100644 index 00000000..502ccde5 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/behavior/VerticalScrollingBehavior.java @@ -0,0 +1,145 @@ +package org.nv95.openmanga.common.views.behavior; + +import android.content.Context; +import android.os.Parcelable; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.WindowInsetsCompat; +import android.util.AttributeSet; +import android.view.View; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by koitharu on 24.12.17. + */ + +abstract class VerticalScrollingBehavior extends CoordinatorLayout.Behavior { + + private int mTotalDyUnconsumed = 0; + private int mTotalDy = 0; + @ScrollDirection + private int mOverScrollDirection = ScrollDirection.SCROLL_NONE; + @ScrollDirection + private int mScrollDirection = ScrollDirection.SCROLL_NONE; + + public VerticalScrollingBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public VerticalScrollingBehavior() { + super(); + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ScrollDirection.SCROLL_DIRECTION_UP, ScrollDirection.SCROLL_DIRECTION_DOWN}) + public @interface ScrollDirection { + int SCROLL_DIRECTION_UP = 1; + int SCROLL_DIRECTION_DOWN = -1; + int SCROLL_NONE = 0; + } + + + /* + @return Overscroll direction: SCROLL_DIRECTION_UP, CROLL_DIRECTION_DOWN, SCROLL_NONE + */ + @ScrollDirection + public int getOverScrollDirection() { + return mOverScrollDirection; + } + + + /** + * @return Scroll direction: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN, SCROLL_NONE + */ + + @ScrollDirection + public int getScrollDirection() { + return mScrollDirection; + } + + + /** + * @param coordinatorLayout CoordinatoyLayout parent + * @param child View child + * @param direction Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN + * @param currentOverScroll Unconsumed value, negative or positive based on the direction; + * @param totalOverScroll Cumulative value for current direction + */ + public abstract void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll); + + /** + * @param scrollDirection Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN + */ + public abstract void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection); + + @Override + public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) { + return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; + } + + @Override + public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) { + super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); + } + + @Override + public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { + super.onStopNestedScroll(coordinatorLayout, child, target); + } + + @Override + public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); + if (dyUnconsumed > 0 && mTotalDyUnconsumed < 0) { + mTotalDyUnconsumed = 0; + mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP; + } else if (dyUnconsumed < 0 && mTotalDyUnconsumed > 0) { + mTotalDyUnconsumed = 0; + mOverScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN; + } + mTotalDyUnconsumed += dyUnconsumed; + onNestedVerticalOverScroll(coordinatorLayout, child, mOverScrollDirection, dyConsumed, mTotalDyUnconsumed); + } + + @Override + public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) { + super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); + if (dy > 0 && mTotalDy < 0) { + mTotalDy = 0; + mScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP; + } else if (dy < 0 && mTotalDy > 0) { + mTotalDy = 0; + mScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN; + } + mTotalDy += dy; + onDirectionNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, mScrollDirection); + } + + @Override + public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed) { + super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed); + mScrollDirection = velocityY > 0 ? ScrollDirection.SCROLL_DIRECTION_UP : ScrollDirection.SCROLL_DIRECTION_DOWN; + return onNestedDirectionFling(coordinatorLayout, child, target, velocityX, velocityY, mScrollDirection); + } + + protected abstract boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection); + + @Override + public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) { + return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); + } + + @Override + public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets) { + return super.onApplyWindowInsets(coordinatorLayout, child, insets); + } + + @Override + public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) { + return super.onSaveInstanceState(parent, child); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/preferences/CategoriesSelectPreference.java b/app/src/main/java/org/nv95/openmanga/common/views/preferences/CategoriesSelectPreference.java new file mode 100644 index 00000000..fc94291e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/preferences/CategoriesSelectPreference.java @@ -0,0 +1,72 @@ +package org.nv95.openmanga.common.views.preferences; + +import android.content.Context; +import android.os.Build; +import android.preference.MultiSelectListPreference; +import android.support.annotation.RequiresApi; +import android.util.AttributeSet; + +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.storage.db.CategoriesRepository; +import org.nv95.openmanga.core.storage.db.CategoriesSpecification; + +import java.util.ArrayList; + +/** + * TODO + */ +public final class CategoriesSelectPreference extends MultiSelectListPreference { + + public CategoriesSelectPreference(Context context) { + super(context); + init(context); + } + + public CategoriesSelectPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public CategoriesSelectPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public CategoriesSelectPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context); + } + + private void init(Context context) { + final ArrayList categories = CategoriesRepository.get(context).query(new CategoriesSpecification().orderByDate(true)); + final int length = categories == null ? 0 : categories.size(); + final CharSequence[] entries = new CharSequence[length]; + final CharSequence[] entryValues = new CharSequence[length]; + for (int i = 0; i < length; i++) { + final Category o = categories.get(i); + entries[i] = o.name; + entryValues[i] = String.valueOf(o.id); + } + setEntries(entries); + setEntryValues(entryValues); + } + + /*@Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + builder.setNeutralButton(R.string.all, this); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_NEUTRAL) { + if (dialog instanceof AlertDialog) { + LayoutUtils.checkAll(((AlertDialog) dialog).getListView()); + } + } else { + super.onClick(dialog, which); + } + }*/ +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/preferences/ColorPreference.java b/app/src/main/java/org/nv95/openmanga/common/views/preferences/ColorPreference.java new file mode 100644 index 00000000..ae4f64c1 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/preferences/ColorPreference.java @@ -0,0 +1,130 @@ +package org.nv95.openmanga.common.views.preferences; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.preference.DialogPreference; +import android.support.annotation.ColorInt; +import android.support.v7.widget.AppCompatSeekBar; +import android.util.AttributeSet; +import android.view.View; +import android.widget.SeekBar; + +import org.nv95.openmanga.R; + +public final class ColorPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener { + + @ColorInt + private int mValue; + private boolean mValueSet = false; + private final int[] mColor = new int[3]; + private View mViewSample; + + public ColorPreference(Context context) { + this(context, null,android.R.attr.dialogPreferenceStyle); + } + + public ColorPreference(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.dialogPreferenceStyle); + } + + public ColorPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setDialogLayoutResource(R.layout.dialog_pref_color); + } + + @Override + protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { + setValue(restorePersistedValue ? getPersistedInt(mValue) + : (Integer) defaultValue); + } + + private void setValue(@ColorInt int value) { + final boolean changed = mValue != value; + if (changed || !mValueSet) { + mValue = value; + mValueSet = true; + persistInt(value); + if (changed) { + notifyChanged(); + } + } + } + + @ColorInt + public int getColor() { + return mValue; + } + + @Override + protected View onCreateDialogView() { + View view = super.onCreateDialogView(); + initSeekBar(view.findViewById(R.id.seekBar_red)); + initSeekBar(view.findViewById(R.id.seekBar_green)); + initSeekBar(view.findViewById(R.id.seekBar_blue)); + mViewSample = view.findViewById(R.id.view_sample); + return view; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + int i = Integer.parseInt((String) seekBar.getTag()); + mColor[i] = progress; + mViewSample.setBackgroundColor(Color.rgb( + mColor[0], + mColor[1], + mColor[2] + )); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + mColor[0] = Color.red(mValue); + mColor[1] = Color.green(mValue); + mColor[2] = Color.blue(mValue); + view.findViewById(R.id.seekBar_red).setProgress(mColor[0]); + view.findViewById(R.id.seekBar_green).setProgress(mColor[1]); + view.findViewById(R.id.seekBar_blue).setProgress(mColor[2]); + mViewSample.setBackgroundColor(Color.rgb( + mColor[0], + mColor[1], + mColor[2])); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + final int newValue = Color.rgb( + mColor[0], + mColor[1], + mColor[2] + ); + if (callChangeListener(newValue)) { + setValue(newValue); + } + } + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getInt(index, 0); + } + + private void initSeekBar(AppCompatSeekBar seekBar) { + seekBar.setPadding(0, 0, 0, 0); + seekBar.setOnSeekBarChangeListener(this); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/preferences/IntPickerPreference.java b/app/src/main/java/org/nv95/openmanga/common/views/preferences/IntPickerPreference.java new file mode 100644 index 00000000..5fa8dc87 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/preferences/IntPickerPreference.java @@ -0,0 +1,98 @@ +package org.nv95.openmanga.common.views.preferences; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.NumberPicker; + +import org.nv95.openmanga.R; + +public final class IntPickerPreference extends DialogPreference implements IntegerPreference, NumberPicker.OnValueChangeListener { + + private int mValue; + private boolean mValueSet; + private final int mMinValue; + private final int mMaxValue; + private int mNewValue; + + public IntPickerPreference(Context context) { + this(context, null, android.R.attr.dialogPreferenceStyle); + } + + public IntPickerPreference(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.dialogPreferenceStyle); + } + + public IntPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setDialogLayoutResource(R.layout.dialog_pref_intpicker); + final TypedArray a = getContext().obtainStyledAttributes( + attrs, R.styleable.IntSelectPreferenceAttrs); + mMinValue = a.getInt(R.styleable.IntSelectPreferenceAttrs_minValue, 0); + mMaxValue = a.getInt(R.styleable.IntSelectPreferenceAttrs_maxValue, 100); + a.recycle(); + } + + public void setValue(int newValue) { + // Always persist/notify the first time. + final boolean changed = mValue != newValue; + if (changed || !mValueSet) { + mValue = newValue; + mValueSet = true; + persistInt(newValue); + if(changed) { + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + } + } + + @Override + public int getValue() { + return mValue; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + final NumberPicker picker = view.findViewById(R.id.numberPicker); + picker.setMinValue(mMinValue); + picker.setMaxValue(mMaxValue); + picker.setValue(mValue); + picker.setOnValueChangedListener(this); + mNewValue = mValue; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + if (callChangeListener(mNewValue)) { + setValue(mNewValue); + } + } + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getInt(index, 0); + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setValue(restoreValue ? getPersistedInt(mValue) : (Integer) defaultValue); + } + + @Override + public boolean shouldDisableDependents() { + return mValue == 0 || super.shouldDisableDependents(); + } + + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mNewValue = newVal; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/preferences/IntegerPreference.java b/app/src/main/java/org/nv95/openmanga/common/views/preferences/IntegerPreference.java new file mode 100644 index 00000000..f1401793 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/preferences/IntegerPreference.java @@ -0,0 +1,9 @@ +package org.nv95.openmanga.common.views.preferences; + +/** + * Created by koitharu on 31.12.17. + */ + +public interface IntegerPreference { + int getValue(); +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/common/views/preferences/SeekBarPreference.java b/app/src/main/java/org/nv95/openmanga/common/views/preferences/SeekBarPreference.java new file mode 100644 index 00000000..ae28aa78 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/preferences/SeekBarPreference.java @@ -0,0 +1,108 @@ +package org.nv95.openmanga.common.views.preferences; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.preference.Preference; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatImageView; +import android.support.v7.widget.AppCompatSeekBar; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import org.nv95.openmanga.R; + +/** + * Created by koitharu on 06.02.18. + */ + +public final class SeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener, + IntegerPreference { + + @Nullable + private Drawable mIcon; + private int mMax; + private String mSummaryPattern; + private int mValue; + private boolean mValueSet = false; + + public SeekBarPreference(Context context) { + this(context, null, 0); + } + + public SeekBarPreference(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setLayoutResource(R.layout.pref_seekbar); + final TypedArray a = getContext().obtainStyledAttributes( + attrs, R.styleable.SeekBarPreferenceAttrs); + mIcon = a.getDrawable(R.styleable.SeekBarPreferenceAttrs_iconDrawable); + mMax = a.getInt(R.styleable.SeekBarPreferenceAttrs_max, 100); + mSummaryPattern = a.getString(R.styleable.SeekBarPreferenceAttrs_summaryPattern); + a.recycle(); + } + + @Override + protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { + setValue(restorePersistedValue ? getPersistedInt(mValue) + : (Integer) defaultValue); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + final AppCompatSeekBar seekBar = view.findViewById(R.id.seekBar); + seekBar.setOnSeekBarChangeListener(this); + seekBar.setMax(mMax); + seekBar.setProgress(mValue); + view.findViewById(R.id.icon).setImageDrawable(mIcon); + view.findViewById(R.id.title).setText(getTitle()); + view.findViewById(R.id.value).setText(TextUtils.isEmpty(mSummaryPattern) ? String.valueOf(mValue) : + String.format(mSummaryPattern, mValue)); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + setValue(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + + private void setValue(int value) { + final boolean changed = mValue != value; + if (changed || !mValueSet) { + mValue = value; + mValueSet = true; + persistInt(value); + if (changed) { + notifyChanged(); + } + } + } + + @Override + public int getValue() { + return mValue; + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getInt(index, 0); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/HeaderDividerItemDecoration.java b/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/HeaderDividerItemDecoration.java new file mode 100644 index 00000000..b7a4f602 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/HeaderDividerItemDecoration.java @@ -0,0 +1,70 @@ +package org.nv95.openmanga.common.views.recyclerview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.view.View; +/** + * Created by koitharu on 26.12.17. + */ + +public final class HeaderDividerItemDecoration extends RecyclerView.ItemDecoration { + + private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; + + private Drawable mDivider; + + private final Rect mBounds = new Rect(); + + public HeaderDividerItemDecoration(Context context) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + } + + @Override + public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { + if (parent.getLayoutManager() == null || mDivider == null) { + return; + } + canvas.save(); + final int left; + final int right; + if (parent.getClipToPadding()) { + left = parent.getPaddingLeft(); + right = parent.getWidth() - parent.getPaddingRight(); + canvas.clipRect(left, parent.getPaddingTop(), right, + parent.getHeight() - parent.getPaddingBottom()); + } else { + left = 0; + right = parent.getWidth(); + } + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + if (child.getTag() == null || i == childCount - 1 || parent.getChildAt(i + 1).getTag() == null) { + continue; + } + parent.getDecoratedBoundsWithMargins(child, mBounds); + final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); + final int top = bottom - mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(canvas); + } + canvas.restore(); + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + if (mDivider == null || view.getTag() == null) { + outRect.set(0, 0, 0, 0); + return; + } + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/SpaceItemDecoration.java b/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/SpaceItemDecoration.java new file mode 100644 index 00000000..0a5ea5af --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/SpaceItemDecoration.java @@ -0,0 +1,24 @@ +package org.nv95.openmanga.common.views.recyclerview; + +import android.graphics.Rect; +import android.support.annotation.Px; +import android.support.v7.widget.RecyclerView; +import android.view.View; +/** + * Created by koitharu on 13.01.18. + */ + +public final class SpaceItemDecoration extends RecyclerView.ItemDecoration { + + @Px + private final int mSpacing; + + public SpaceItemDecoration(@Px int spacing) { + mSpacing = spacing; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.set(mSpacing, mSpacing, mSpacing, mSpacing); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/SwipeRemoveHelper.java b/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/SwipeRemoveHelper.java new file mode 100644 index 00000000..dad031eb --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/common/views/recyclerview/SwipeRemoveHelper.java @@ -0,0 +1,106 @@ +package org.nv95.openmanga.common.views.recyclerview; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.view.View; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ResourceUtils; + +/** + * Created by koitharu on 18.01.18. + */ + +public final class SwipeRemoveHelper extends ItemTouchHelper.Callback { + + private static final int MOVEMENT_FLAGS = makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT); + + @NonNull + private final OnItemRemovedListener mListener; + private final Drawable mBackground; + private final Drawable mIcon; + private final int mPadding; + + private SwipeRemoveHelper(Context context, @NonNull OnItemRemovedListener listener, @ColorRes int color, @DrawableRes int icon) { + mListener = listener; + mBackground = new ColorDrawable(ContextCompat.getColor(context, color)); + mIcon = ContextCompat.getDrawable(context, icon); + mPadding = ResourceUtils.dpToPx(context.getResources(), 24); + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + return MOVEMENT_FLAGS; + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + final int pos = viewHolder.getAdapterPosition(); + mListener.onItemRemoved(pos); + } + + @Override + public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + if (viewHolder.getAdapterPosition() == -1) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + return; + } + final View itemView = viewHolder.itemView; + if (dX < 0) { + mBackground.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom()); + mBackground.draw(c); + + int itemHeight = itemView.getBottom() - itemView.getTop(); + int intrinsicWidth = mIcon.getIntrinsicWidth(); + int intrinsicHeight = mIcon.getIntrinsicWidth(); + + int xMarkLeft = itemView.getRight() - mPadding - intrinsicWidth; + int xMarkRight = itemView.getRight() - mPadding; + int xMarkTop = itemView.getTop() + (itemHeight - intrinsicHeight)/2; + int xMarkBottom = xMarkTop + intrinsicHeight; + mIcon.setBounds(xMarkLeft, xMarkTop, xMarkRight, xMarkBottom); + mIcon.draw(c); + } else if (dX > 0) { + mBackground.setBounds(itemView.getLeft(), itemView.getTop(), itemView.getLeft() + (int) dX, itemView.getBottom()); + mBackground.draw(c); + + int itemHeight = itemView.getBottom() - itemView.getTop(); + int intrinsicWidth = mIcon.getIntrinsicWidth(); + int intrinsicHeight = mIcon.getIntrinsicWidth(); + + int xMarkRight = mPadding + intrinsicWidth; + int xMarkTop = itemView.getTop() + (itemHeight - intrinsicHeight)/2; + int xMarkBottom = xMarkTop + intrinsicHeight; + mIcon.setBounds(mPadding, xMarkTop, xMarkRight, xMarkBottom); + mIcon.draw(c); + } + + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + + public static void setup(@NonNull RecyclerView recyclerView, @NonNull OnItemRemovedListener listener) { + setup(recyclerView, listener, R.color.red_overlay, R.drawable.ic_trash_white); + } + + public static void setup(@NonNull RecyclerView recyclerView, @NonNull OnItemRemovedListener listener, @ColorRes int color, @DrawableRes int icon) { + new ItemTouchHelper(new SwipeRemoveHelper(recyclerView.getContext(), listener, color, icon)).attachToRecyclerView(recyclerView); + } + + public interface OnItemRemovedListener { + + void onItemRemoved(int position); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/components/AppCompatProgressBar.java b/app/src/main/java/org/nv95/openmanga/components/AppCompatProgressBar.java deleted file mode 100644 index 41a01d5d..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/AppCompatProgressBar.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.nv95.openmanga.components; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.widget.ProgressBar; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * Created by nv95 on 10.07.16. - */ - -public class AppCompatProgressBar extends ProgressBar { - - private PorterDuffColorFilter mColorFilter; - - public AppCompatProgressBar(Context context) { - super(context); - init(context); - } - - public AppCompatProgressBar(Context context, AttributeSet attrs) { - super(context, attrs); - init(context); - } - - public AppCompatProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public AppCompatProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(context); - } - - private void init(Context context) { - mColorFilter = new PorterDuffColorFilter(LayoutUtils.getAttrColor(context, R.attr.colorAccent), PorterDuff.Mode.SRC_IN); - applyColorFilter(getProgressDrawable()); - applyColorFilter(getIndeterminateDrawable()); - } - - private void applyColorFilter(@Nullable Drawable d) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && d != null) { - d.setColorFilter(mColorFilter); - } - } - - @Override - public void setProgressDrawable(Drawable d) { - applyColorFilter(d); - super.setProgressDrawable(d); - } - - @Override - public void setIndeterminateDrawable(Drawable d) { - applyColorFilter(d); - super.setIndeterminateDrawable(d); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/AutoHeightLayout.java b/app/src/main/java/org/nv95/openmanga/components/AutoHeightLayout.java deleted file mode 100644 index 99e8dd93..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/AutoHeightLayout.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.nv95.openmanga.components; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.util.AttributeSet; - -/** - * Created by nv95 on 25.01.16. - */ -public class AutoHeightLayout extends FrameCheckLayout { - - private double mAspectRatio = 18f / 13f; - - public AutoHeightLayout(Context context) { - super(context); - } - - public AutoHeightLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AutoHeightLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public AutoHeightLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public void setAspectRatio(float value) { - mAspectRatio = value; - } - - public void setAspectRatio(int height, int width) { - mAspectRatio = height / (float)width; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int originalWidth = MeasureSpec.getSize(widthMeasureSpec); - int calculatedHeight = (int) (originalWidth * mAspectRatio); - super.onMeasure( - MeasureSpec.makeMeasureSpec(originalWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(calculatedHeight, MeasureSpec.EXACTLY) - ); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/CenterAlignBehavior.java b/app/src/main/java/org/nv95/openmanga/components/CenterAlignBehavior.java deleted file mode 100644 index d402a649..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/CenterAlignBehavior.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.nv95.openmanga.components; - -import android.content.Context; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.CoordinatorLayout; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.view.View; - -/** - * Created by admin on 05.07.17. - */ - -public class CenterAlignBehavior extends CoordinatorLayout.Behavior { - - private int scrollY = 0; - private int maxScroll = 0; - - public CenterAlignBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { - return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; - } - - @Override - public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { - handleScroll(parent, child, 0); - return super.onDependentViewChanged(parent, child, dependency); - } - - @Override - public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { - return dependency instanceof AppBarLayout; - } - - @Override - public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { - handleScroll(coordinatorLayout, child, dy); - } - - private void handleScroll(CoordinatorLayout coordinatorLayout, View child, int dy) { - if (maxScroll == 0) { - for (int i=0;i maxScroll) { - scrollY = maxScroll; - } else if (scrollY < 0) { - scrollY = 0; - } - int offset = (scrollY - maxScroll) / 2; - child.setTranslationY(offset); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/DividerItemDecoration.java b/app/src/main/java/org/nv95/openmanga/components/DividerItemDecoration.java deleted file mode 100644 index 43b107e5..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/DividerItemDecoration.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.nv95.openmanga.components; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.support.annotation.DrawableRes; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * source: - * https://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview - */ - -public class DividerItemDecoration extends RecyclerView.ItemDecoration { - - private Drawable mDivider; - - /** - * Default divider will be used - */ - public DividerItemDecoration(Context context) { - mDivider = LayoutUtils.getAttrDrawable(context, android.R.attr.listDivider); - } - - /** - * Custom divider will be used - */ - public DividerItemDecoration(Context context, @DrawableRes int resId) { - mDivider = ContextCompat.getDrawable(context, resId); - } - - @Override - public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { - int left = parent.getPaddingLeft(); - int right = parent.getWidth() - parent.getPaddingRight(); - - int childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = parent.getChildAt(i); - - RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); - - int top = child.getBottom() + params.bottomMargin; - int bottom = top + mDivider.getIntrinsicHeight(); - - mDivider.setBounds(left, top, right, bottom); - mDivider.draw(c); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/ExtraCheckable.java b/app/src/main/java/org/nv95/openmanga/components/ExtraCheckable.java deleted file mode 100644 index e2242430..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/ExtraCheckable.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.nv95.openmanga.components; - -import android.widget.Checkable; - -/** - * Created by admin on 20.07.17. - */ - -public interface ExtraCheckable extends Checkable { - - void setCheckedAnimated(boolean checked); -} diff --git a/app/src/main/java/org/nv95/openmanga/components/FABScrollBehavior.java b/app/src/main/java/org/nv95/openmanga/components/FABScrollBehavior.java deleted file mode 100644 index 1f0f7dfc..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/FABScrollBehavior.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.nv95.openmanga.components; - -import android.content.Context; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorListener; -import android.support.v4.view.animation.FastOutSlowInInterpolator; -import android.util.AttributeSet; -import android.view.View; -import android.view.animation.Interpolator; - -/** - * Created by nv95 on 25.01.16. - */ -public class FABScrollBehavior extends FloatingActionButton.Behavior { - - private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); - - private boolean mIsAnimatingOut = false; - private boolean mShowing = true; - - public FABScrollBehavior() { - super(); - } - - public FABScrollBehavior(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - } - - @Override - public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { - super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); - if (dyConsumed > 0 && !this.mIsAnimatingOut && mShowing) { - animateOut(child); - } else if (dyConsumed < 0 && !mShowing) { - animateIn(child); - } - } - - - - @Override - public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) { - return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; - } - - private void animateOut(final FloatingActionButton button) { - ViewCompat.animate(button) - .translationY(button.getMeasuredHeight() + - ((CoordinatorLayout.LayoutParams) button.getLayoutParams()).bottomMargin) - .setInterpolator(INTERPOLATOR).withLayer() - .setListener(new ViewPropertyAnimatorListener() { - public void onAnimationStart(View view) { - mIsAnimatingOut = true; - } - - public void onAnimationCancel(View view) { - mIsAnimatingOut = false; - } - - public void onAnimationEnd(View view) { - mIsAnimatingOut = false; - //view.setVisibility(View.INVISIBLE); - mShowing = false; - } - }).start(); - } - - private void animateIn(FloatingActionButton button) { - mShowing = true; - ViewCompat.animate(button).translationY(0) - .setInterpolator(INTERPOLATOR).withLayer().setListener(null) - .start(); - } - - -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/FrameCheckLayout.java b/app/src/main/java/org/nv95/openmanga/components/FrameCheckLayout.java deleted file mode 100644 index b64d4fa0..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/FrameCheckLayout.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.nv95.openmanga.components; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.AnimUtils; - -/** - * Created by nv95 on 30.06.16. - */ - -public class FrameCheckLayout extends FrameLayout implements ExtraCheckable { - - private boolean mChecked; - private static int mPadding; - private ImageView mCheckMark; - - public FrameCheckLayout(Context context) { - super(context); - init(); - } - - public FrameCheckLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public FrameCheckLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public FrameCheckLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(); - } - - private void init() { - mChecked = false; - if (mPadding == 0) { - mPadding = getResources().getDimensionPixelOffset(R.dimen.padding8); - } - } - - @Override - public void setChecked(boolean checked) { - if (mChecked == checked) { - return; - } - mChecked = checked; - if (mChecked) { - getCheckMark().setVisibility(VISIBLE); - } else { - getCheckMark().setVisibility(View.GONE); - } - } - - - @Override - public void setCheckedAnimated(boolean checked) { - if (mChecked == checked) { - return; - } - mChecked = checked; - if (mChecked) { - AnimUtils.crossfade(null, getCheckMark()); - } else { - AnimUtils.crossfade(getCheckMark(), null); - } - } - - private View getCheckMark() { - if (mCheckMark == null) { - mCheckMark = new ImageView(getContext()); - mCheckMark.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - mCheckMark.setPadding(mPadding, mPadding, mPadding, mPadding); - mCheckMark.setScaleType(ImageView.ScaleType.MATRIX); - //mCheckMark.setImageResource(R.drawable.ic_checkmark); - mCheckMark.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.black_owerlay_40)); - mCheckMark.setVisibility(GONE); - ViewCompat.setElevation(mCheckMark, 10000); - addView(mCheckMark); - } - return mCheckMark; - } - - @Override - public boolean isChecked() { - return mChecked; - } - - @Override - public void toggle() { - setChecked(!mChecked); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/IntSelectPreference.java b/app/src/main/java/org/nv95/openmanga/components/IntSelectPreference.java deleted file mode 100644 index 381f8b82..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/IntSelectPreference.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.nv95.openmanga.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.preference.DialogPreference; -import android.util.AttributeSet; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.GridView; - -import org.nv95.openmanga.R; - -import java.util.ArrayList; - -/** - * Created by admin on 23.07.17. - */ - -public class IntSelectPreference extends DialogPreference implements AdapterView.OnItemClickListener, - IntegerPreference { - - private final int mMaxValue; - private final int mMinValue; - private int mValue; - private int mNewValue; - private boolean mValueSet; - private final ArrayList mValues; - - - public IntSelectPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setDialogLayoutResource(R.layout.pref_intselect); - TypedArray a = getContext().obtainStyledAttributes( - attrs, R.styleable.IntSelectPreferenceAttrs); - mMaxValue = a.getInt(R.styleable.IntSelectPreferenceAttrs_maxValue, 4); - mMinValue = a.getInt(R.styleable.IntSelectPreferenceAttrs_minValue, 0); - mValue = 1; - a.recycle(); - mValueSet = false; - mValues = new ArrayList<>(); - for (int i = mMinValue; i <= mMaxValue; i++) { - mValues.add(i); - } - } - - @Override - protected void onBindDialogView(View view) { - super.onBindDialogView(view); - GridView gridView = view.findViewById(R.id.gridView); - gridView.setNumColumns(Math.min(mValues.size(), 5)); - gridView.setAdapter(new ArrayAdapter<>( - view.getContext(), - R.layout.item_cell_selectable, - android.R.id.text1, - mValues - )); - gridView.setItemChecked(mValues.indexOf(mValue), true); - gridView.setOnItemClickListener(this); - } - - public void setValue(int value) { - // Always persist/notify the first time. - final boolean changed = mValue != value; - if (changed || !mValueSet) { - mValue = value; - mValueSet = true; - persistInt(value); - if (changed) { - notifyChanged(); - } - } - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - super.onDialogClosed(positiveResult); - if (positiveResult) { - OnPreferenceChangeListener changeListener = getOnPreferenceChangeListener(); - if (changeListener == null || changeListener.onPreferenceChange(this, mNewValue)) { - setValue(mNewValue); - } - } - } - - @Override - protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { - if (defaultValue instanceof Integer) { - mNewValue = (int) defaultValue; - } else if (defaultValue instanceof String) { - mNewValue = Integer.parseInt((String) defaultValue); - } - if (restoreValue) { - mNewValue = getPersistedInt(mValue); - } - setValue(mNewValue); - } - - @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - mNewValue = mValues.get(i); - } - - @Override - public int getValue() { - return mValue; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/IntegerPreference.java b/app/src/main/java/org/nv95/openmanga/components/IntegerPreference.java deleted file mode 100644 index 5aa266d0..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/IntegerPreference.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.nv95.openmanga.components; - -/** - * Created by admin on 24.07.17. - */ - -public interface IntegerPreference { - - int getValue(); -} diff --git a/app/src/main/java/org/nv95/openmanga/components/OnboardSnackbar.java b/app/src/main/java/org/nv95/openmanga/components/OnboardSnackbar.java deleted file mode 100644 index 58a8e008..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/OnboardSnackbar.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.nv95.openmanga.components; - -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.design.widget.Snackbar; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * Created by admin on 25.07.17. - */ - -public class OnboardSnackbar implements View.OnClickListener { - - private final Snackbar mSnackbar; - private final View mView; - @Nullable - private View.OnClickListener mActionClickListener; - - - private OnboardSnackbar(Snackbar snackbar) { - mSnackbar = snackbar; - Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) snackbar.getView(); - mView = LayoutInflater.from(layout.getContext()) - .inflate(R.layout.snackbar_onboard, null); - layout.addView(mView, 0); - layout.setBackgroundColor(LayoutUtils.isAppThemeDark(snackbar.getContext()) ? Color.BLACK : Color.WHITE); - mView.findViewById(android.R.id.button1).setOnClickListener(this); - mView.findViewById(android.R.id.button2).setOnClickListener(this); - ((TextView)mView.findViewById(android.R.id.text1)).setTextColor(LayoutUtils.isAppThemeDark(layout.getContext()) ? Color.WHITE : Color.BLACK); - } - - public OnboardSnackbar setText(@StringRes int resId) { - ((TextView)mView.findViewById(android.R.id.text1)).setText(resId); - return this; - } - - public OnboardSnackbar setAction(@StringRes int resId, View.OnClickListener onClickListener) { - Button b = mView.findViewById(android.R.id.button1); - b.setText(resId); - mActionClickListener = onClickListener; - return this; - } - - public OnboardSnackbar setDiscardText(@StringRes int resId) { - Button b = mView.findViewById(android.R.id.button2); - b.setText(resId); - return this; - } - - public void show() { - mSnackbar.show(); - } - - public static OnboardSnackbar make(@NonNull View view, @StringRes int resId, int duration) { - return new OnboardSnackbar(Snackbar.make(view, "", duration)).setText(resId); - } - - @Override - public void onClick(View view) { - if (view.getId() == android.R.id.button1 && mActionClickListener != null) { - mActionClickListener.onClick(view); - } - mSnackbar.dismiss(); - } - - public static boolean askOnce(View view, @StringRes int text, @StringRes int discardText, - @StringRes int acceptText, View.OnClickListener onClickListener) { - if (view != null && view.getVisibility() == View.VISIBLE - && !view.getContext().getSharedPreferences("tips", Context.MODE_PRIVATE).getBoolean("s__" + text, false)) { - - OnboardSnackbar.make(view, text, Snackbar.LENGTH_INDEFINITE) - .setAction(acceptText, onClickListener) - .setDiscardText(discardText) - .show(); - - SharedPreferences prefs = view.getContext().getSharedPreferences("tips", Context.MODE_PRIVATE); - prefs.edit().putBoolean("s__" + text, true).apply(); - return true; - } else { - return false; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/PickerPreference.java b/app/src/main/java/org/nv95/openmanga/components/PickerPreference.java deleted file mode 100644 index 004fb0e3..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/PickerPreference.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.nv95.openmanga.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.preference.DialogPreference; -import android.util.AttributeSet; -import android.view.View; -import android.widget.NumberPicker; - -import org.nv95.openmanga.R; - -/** - * Created by admin on 24.07.17. - */ - -public class PickerPreference extends DialogPreference implements IntegerPreference { - - private final int mMaxValue; - private final int mMinValue; - private int mValue; - private boolean mValueSet; - private NumberPicker mPicker; - - public PickerPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setDialogLayoutResource(R.layout.pref_picker); - TypedArray a = getContext().obtainStyledAttributes( - attrs, R.styleable.IntSelectPreferenceAttrs); - mMaxValue = a.getInt(R.styleable.IntSelectPreferenceAttrs_maxValue, 100); - mMinValue = a.getInt(R.styleable.IntSelectPreferenceAttrs_minValue, 0); - mValue = 100; - a.recycle(); - mValueSet = false; - } - - @Override - protected void onBindDialogView(View view) { - super.onBindDialogView(view); - mPicker = view.findViewById(R.id.numberPicker); - mPicker.setMinValue(mMinValue); - mPicker.setMaxValue(mMaxValue); - mPicker.setValue(mValue); - } - - public void setValue(int value) { - // Always persist/notify the first time. - final boolean changed = mValue != value; - if (changed || !mValueSet) { - mValue = value; - mValueSet = true; - persistInt(value); - if (changed) { - notifyChanged(); - } - } - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - super.onDialogClosed(positiveResult); - if (positiveResult) { - OnPreferenceChangeListener changeListener = getOnPreferenceChangeListener(); - if (changeListener == null || changeListener.onPreferenceChange(this, mPicker.getValue())) { - setValue(mPicker.getValue()); - } - } - } - - @Override - protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { - int newValue = 0; - if (defaultValue instanceof Integer) { - newValue = (int) defaultValue; - } else if (defaultValue instanceof String) { - newValue = Integer.parseInt((String) defaultValue); - } - if (restoreValue) { - newValue = getPersistedInt(mValue); - } - setValue(newValue); - } - - @Override - public int getValue() { - return mValue; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/RatingView.java b/app/src/main/java/org/nv95/openmanga/components/RatingView.java deleted file mode 100644 index bf32a52c..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/RatingView.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.nv95.openmanga.components; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.support.v4.content.ContextCompat; -import android.util.AttributeSet; - -import org.nv95.openmanga.R; - -/** - * Created by nv95 on 05.11.16. - */ - -public class RatingView extends android.support.v7.widget.AppCompatTextView { - - private static final int[] mColors = new int[] { - Color.parseColor("#808080"), - Color.parseColor("#5ab2ff"), - Color.parseColor("#ab4ee5"), - Color.parseColor("#e5483a") - }; - - private Drawable mStarDrawable; - - public RatingView(Context context) { - super(context); - init(context); - } - - public RatingView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context); - } - - public RatingView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context); - } - - private void init(Context context) { - setLines(1); - mStarDrawable = ContextCompat.getDrawable(context, R.drawable.ic_star); - mStarDrawable.setAlpha(200); - setCompoundDrawablesWithIntrinsicBounds(mStarDrawable, null, null, null); - } - - @SuppressLint("DefaultLocale") - public void setRating(byte value) { - int a = value / 10; - int b = value % 10; - setText(String.format("%d.%d", a, b)); - int color; - if (a < 4) { - color = mColors[0]; - } else if (a < 7) { - color = mColors[1]; - } else if (a < 9) { - color = mColors[2]; - } else { - color = mColors[3]; - } - mStarDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/ReaderMenu.java b/app/src/main/java/org/nv95/openmanga/components/ReaderMenu.java deleted file mode 100644 index 49d57773..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/ReaderMenu.java +++ /dev/null @@ -1,406 +0,0 @@ -package org.nv95.openmanga.components; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorCompat; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.PopupMenu; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.components.reader.recyclerpager.RecyclerViewPager; -import org.nv95.openmanga.dialogs.NavigationListener; -import org.nv95.openmanga.items.Bookmark; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.BookmarksProvider; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.NewChaptersProvider; -import org.nv95.openmanga.utils.ChangesObserver; - -import java.util.ArrayList; -import java.util.TreeSet; - -/** - * Created by nv95 on 17.11.16. - */ - -public class ReaderMenu extends FrameLayout implements View.OnClickListener, View.OnLongClickListener, - RecyclerViewPager.OnPageChangedListener, PopupMenu.OnMenuItemClickListener, NavigationListener { - - private boolean mIsStatusBar; - private int mLimitYStatusBar; - - private MangaSummary mManga; - - private ImageView[] mButtons; - private TextView mTitle; - private ProgressBar mProgressBar; - private View[] mMenus; - private boolean mVisible; - private TreeSet mBookmarks; - - private PopupMenu mSaveMenu; - private PopupMenu mOptionsMenu; - - @Nullable - private Callback mCallback; - - public ReaderMenu(Context context) { - super(context); - init(context); - } - - public ReaderMenu(Context context, AttributeSet attrs) { - super(context, attrs); - init(context); - } - - public ReaderMenu(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public ReaderMenu(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(context); - } - - public void setCallback(@Nullable Callback callback) { - mCallback = callback; - } - - private void init(Context context) { - mVisible = false; - mBookmarks = new TreeSet<>(); - LayoutInflater.from(context).inflate(R.layout.readermenu, this, true); - mLimitYStatusBar = getContext().getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin); - mProgressBar = findViewById(R.id.progressBar); - mTitle = findViewById(android.R.id.title); - mMenus = new View[] { - findViewById(R.id.menu_top), - findViewById(R.id.menu_bottom) - }; - mButtons = new ImageView[] { - findViewById(android.R.id.home), - findViewById(R.id.menuitem_favourite), - findViewById(R.id.menuitem_unfavourite), - findViewById(R.id.menuitem_save), - findViewById(R.id.menuitem_bookmark), - findViewById(R.id.menuitem_rotation), - findViewById(R.id.menuitem_settings), - findViewById(R.id.menuitem_thumblist), - findViewById(R.id.menuitem_unbookmark), - }; - for (ImageView o : mButtons) { - o.setOnClickListener(this); - o.setOnLongClickListener(this); - } - mTitle.setOnClickListener(this); - mProgressBar.setOnClickListener(this); - mSaveMenu = new PopupMenu(context, mButtons[3]); - mSaveMenu.inflate(R.menu.save); - mSaveMenu.setOnMenuItemClickListener(this); - - mOptionsMenu = new PopupMenu(context, mButtons[6]); - mOptionsMenu.inflate(R.menu.read_prefs); - mOptionsMenu.setOnMenuItemClickListener(this); - } - - /** - * Для устройств с системными кнопками - * При проведении снизу в верх или сверзу вниз у края экрана, для отображения системных кнопок - * @param ev - * @return - */ - - @Override - public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - if (mVisible) { - hide(); - } - if (ev.getY() < mLimitYStatusBar || (getHeight() - ev.getY() < (mLimitYStatusBar * 2))) { - mIsStatusBar = true; - return true; - } - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_HOVER_MOVE: - if (mIsStatusBar) - return true; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mIsStatusBar = false; - break; - } - return mIsStatusBar || super.onTouchEvent(ev); - } - - public void setData(MangaSummary manga) { - mManga = manga; - mTitle.setText(manga.name); - mBookmarks.clear(); - } - - public void updateMenu() { - int fav = FavouritesProvider.getInstance(getContext()).getCategory(mManga); - mButtons[1].setVisibility(fav == -1 ? VISIBLE : GONE); - mButtons[2].setVisibility(fav != -1 ? VISIBLE : GONE); - } - - public void show() { - updateMenu(); - if (mVisible) { - return; - } - mVisible = true; - requestLayout(); - if (mCallback != null) { - mCallback.onVisibilityChanged(true); - } - mMenus[0].setTranslationY(-mMenus[0].getHeight()); - mMenus[1].setTranslationY(mMenus[1].getHeight()); - ViewPropertyAnimatorCompat[] animators = new ViewPropertyAnimatorCompat[] { - ViewCompat.animate(mMenus[0]).translationY(0f).setDuration(250).withStartAction(mShowRunnable), - ViewCompat.animate(mMenus[1]).translationY(0f).setDuration(250).withStartAction(mShowRunnable) - }; - animators[0].start(); - animators[1].start(); - } - - public void hide() { - if (!mVisible) { - return; - } - mVisible = false; - if (mCallback != null) { - mCallback.onVisibilityChanged(false); - } - ViewPropertyAnimatorCompat[] animators = new ViewPropertyAnimatorCompat[] { - ViewCompat.animate(mMenus[0]).translationY(-mMenus[0].getHeight()).setDuration(250).withEndAction(mHideRunnable), - ViewCompat.animate(mMenus[1]).translationY(mMenus[1].getHeight()).setDuration(250).withEndAction(mHideRunnable) - }; - animators[0].start(); - animators[1].start(); - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.menuitem_unfavourite: - case R.id.menuitem_favourite: - final FavouritesProvider favouritesProvider = FavouritesProvider.getInstance(getContext()); - FavouritesProvider.dialog(getContext(), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - hide(); - if (which == DialogInterface.BUTTON_NEUTRAL) { - if (favouritesProvider.remove(mManga)) { - ChangesObserver.getInstance().emitOnFavouritesChanged(mManga, -1); - Snackbar.make(ReaderMenu.this, R.string.unfavourited, Snackbar.LENGTH_SHORT).show(); - } - } else { - NewChaptersProvider.getInstance(getContext()) - .storeChaptersCount(mManga.id, mManga.getChapters().size()); - ChangesObserver.getInstance().emitOnFavouritesChanged(mManga, which); - Snackbar.make(ReaderMenu.this, R.string.favourited, Snackbar.LENGTH_SHORT).show(); - } - } - }, mManga); - break; - case R.id.menuitem_settings: - Menu menu = mOptionsMenu.getMenu(); - menu.findItem(R.id.action_webmode).setChecked( - HistoryProvider.getInstance(view.getContext()).isWebMode(mManga) - ); - mOptionsMenu.show(); - break; - case R.id.menuitem_save: - menu = mSaveMenu.getMenu(); - if (LocalMangaProvider.class.equals(mManga.provider)) { - menu.findItem(R.id.action_save).setVisible(false); - menu.findItem(R.id.action_save_more).setVisible(mManga.status == MangaInfo.STATUS_ONGOING); - } else { - menu.findItem(R.id.action_save).setVisible(true); - menu.findItem(R.id.action_save_more).setVisible(false); - } - mSaveMenu.show(); - break; - default: - if (mCallback != null) { - mCallback.onActionClick(view.getId()); - } - } - } - - public void onBookmarkAdded(Bookmark bookmark) { - mBookmarks.add(bookmark.page); - if (bookmark.page == mProgressBar.getProgress()) { - mButtons[4].setVisibility(GONE); - mButtons[8].setVisibility(VISIBLE); - } - } - - public void onBookmarkRemoved(int pos) { - mBookmarks.remove(pos); - if (pos == mProgressBar.getProgress()) { - mButtons[8].setVisibility(GONE); - mButtons[4].setVisibility(VISIBLE); - } - } - - @Override - public boolean onLongClick(View view) { - if (view instanceof ImageView) { - CharSequence title = view.getContentDescription(); - Toast toast = Toast.makeText(view.getContext(), title, Toast.LENGTH_SHORT); - toast.setGravity(Gravity.CENTER, 0, 0); - toast.show(); - return true; - } - return false; - } - - @Override - public void OnPageChanged(int oldPosition, int newPosition) { - mProgressBar.setProgress(newPosition); - if (mBookmarks.contains(newPosition)) { - mButtons[4].setVisibility(GONE); - mButtons[8].setVisibility(VISIBLE); - } else { - mButtons[8].setVisibility(GONE); - mButtons[4].setVisibility(VISIBLE); - } - } - - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - switch (menuItem.getItemId()) { - case R.id.action_webmode: - menuItem.setChecked(!menuItem.isChecked()); - HistoryProvider.getInstance(getContext()).setWebMode(mManga, menuItem.isChecked()); - default: - if (mCallback != null) { - mCallback.onActionClick(menuItem.getItemId()); - } - } - return true; - } - - public void onChapterChanged(MangaChapter chapter, int pagesCount) { - mBookmarks.clear(); - mProgressBar.setMax(pagesCount); - ArrayList bookmarks = BookmarksProvider.getInstance(getContext()) - .getAll(mManga.id, chapter.number); - for (Bookmark o : bookmarks) { - mBookmarks.add(o.page); - } - } - - @Override - public void onPageChange(int page) { - if (mCallback != null) { - mCallback.onPageChanged(page); - } - } - - public boolean isVisible() { - return mVisible; - } - - public interface Callback { - void onActionClick(int id); - void onPageChanged(int index); - void onVisibilityChanged(boolean visible); - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState ss = new SavedState(superState); - ss.visible = mVisible; - return ss; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if(!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - SavedState ss = (SavedState)state; - super.onRestoreInstanceState(ss.getSuperState()); - mVisible = ss.visible; - mMenus[0].setVisibility(mVisible ? VISIBLE : GONE); - mMenus[1].setVisibility(mVisible ? VISIBLE : GONE); - } - - static class SavedState extends BaseSavedState { - - boolean visible; - - SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - visible = in.readByte() == 1; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeByte((byte) (visible ? 1 : 0)); - } - - //required field that makes Parcelables from a Parcel - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - private final Runnable mShowRunnable = new Runnable() { - @Override - public void run() { - mMenus[0].setVisibility(VISIBLE); - mMenus[1].setVisibility(VISIBLE); - } - }; - - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - mMenus[0].setVisibility(INVISIBLE); - mMenus[1].setVisibility(INVISIBLE); - } - }; -} diff --git a/app/src/main/java/org/nv95/openmanga/components/SearchInput.java b/app/src/main/java/org/nv95/openmanga/components/SearchInput.java deleted file mode 100644 index 087e7eea..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/SearchInput.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.nv95.openmanga.components; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.support.annotation.AttrRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StyleRes; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.AnimUtils; - -/** - * Created by nv95 on 22.12.16. - */ - -public class SearchInput extends FrameLayout implements TextWatcher, View.OnFocusChangeListener { - - private EditText mEditText; - private ImageView mImageViewClear; - private boolean mClearVisible; - @Nullable - private OnFocusChangeListener mFocusChangeListener; - @Nullable - private OnTextChangedListener mTextChangedListener; - - public SearchInput(@NonNull Context context) { - super(context); - init(context); - } - - public SearchInput(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(context); - } - - public SearchInput(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public SearchInput(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(context); - } - - private void init(Context context) { - LayoutInflater.from(context) - .inflate(R.layout.layout_search, this, true); - mEditText = findViewById(R.id.editTextQuery); - mImageViewClear = findViewById(R.id.image_clear); - mFocusChangeListener = null; - mClearVisible = false; - mImageViewClear.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - mEditText.getText().clear(); - } - }); - mEditText.addTextChangedListener(this); - mEditText.setOnFocusChangeListener(this); - } - - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - updateClearButton(!TextUtils.isEmpty(charSequence) && mEditText.hasFocus()); - if (mTextChangedListener != null) { - mTextChangedListener.onTextChanged(charSequence); - } - } - - @Override - public void afterTextChanged(Editable editable) { - - } - - @Override - public void onFocusChange(View view, boolean b) { - updateClearButton(!TextUtils.isEmpty(mEditText.getText()) && b); - if (mFocusChangeListener != null) { - mFocusChangeListener.onFocusChange(view, b); - } - } - - private void updateClearButton(boolean show) { - if (show != mClearVisible) { - mClearVisible = show; - if (show) { - AnimUtils.zooma(null, mImageViewClear); - } else { - AnimUtils.zooma(mImageViewClear, null); - } - } - } - - public EditText getEditText() { - return mEditText; - } - - public void setOnEditFocusChangeListener(@Nullable OnFocusChangeListener listener) { - mFocusChangeListener = listener; - } - - public void setOnTextChangedListener(@Nullable OnTextChangedListener listener) { - mTextChangedListener = listener; - } - - public void setText(CharSequence charSequence) { - mEditText.setText(charSequence); - mEditText.setSelection(mEditText.getText().length()); - } - - public interface OnTextChangedListener { - void onTextChanged(CharSequence text); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/SeekBarPreference.java b/app/src/main/java/org/nv95/openmanga/components/SeekBarPreference.java deleted file mode 100644 index 91948e74..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/SeekBarPreference.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.nv95.openmanga.components; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.preference.Preference; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.AppCompatSeekBar; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.SeekBar; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * Created by nv95 on 12.02.16. - */ -public class SeekBarPreference extends Preference implements AppCompatSeekBar.OnSeekBarChangeListener { - - private TextView mTextView; - private AppCompatSeekBar mSeekBar; - private int mValue; - private int mMax; - @Nullable - private Drawable mIcon; - private boolean mValueSet; - private final boolean mDarkTheme; - - public SeekBarPreference(Context context, AttributeSet attrs) { - super(context, attrs); - TypedArray a = getContext().obtainStyledAttributes( - attrs, R.styleable.SeekBarPreferenceAttrs); - mIcon = a.getDrawable(R.styleable.SeekBarPreferenceAttrs_iconDrawable); - mMax = a.getInt(R.styleable.SeekBarPreferenceAttrs_max, 100); - mValue = 20; - a.recycle(); - mValueSet = false; - mDarkTheme = LayoutUtils.isAppThemeDark(context); - } - - @SuppressLint("MissingSuperCall") - @Override - protected View onCreateView(ViewGroup parent) { - LinearLayout layout = (LinearLayout) LayoutInflater.from( - getContext()) - .inflate(R.layout.pref_seekbar, parent, false); - ((TextView) layout.findViewById(R.id.title)).setText(getTitle()); - mSeekBar = layout.findViewById(R.id.seekBar); - mSeekBar.setMax(mMax); - mSeekBar.setProgress(mValue); - mSeekBar.setOnSeekBarChangeListener(this); - ImageView imageView = layout.findViewById(R.id.icon); - if (mIcon != null) { - imageView.setVisibility(View.VISIBLE); - imageView.setImageDrawable(mIcon); - } - mTextView = layout.findViewById(R.id.value); - mTextView.setText(String.valueOf(mValue)); - updateIcon(); - return layout; - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - updateIcon(); - } - - private void updateIcon() { - if (mIcon != null) { - int color = ContextCompat.getColor( - getContext(), - isEnabled() ? (mDarkTheme ? R.color.white_overlay_85 : android.R.color.black) : (mDarkTheme ? R.color.dark_disabled : R.color.white_overlay_85) - ); - mIcon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - } - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, - boolean fromUser) { - mTextView.setText(String.valueOf(progress)); - mTextView.invalidate(); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - setValue(seekBar.getProgress()); - } - - public void setValue(int value) { - // Always persist/notify the first time. - final boolean changed = mValue != value; - if (changed || !mValueSet) { - mValue = value; - mValueSet = true; - persistInt(value); - if (changed) { - notifyChanged(); - } - } - } - - @Override - protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { - setValue(restoreValue ? getPersistedInt(mValue) : (int) defaultValue); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/TopCropImageView.java b/app/src/main/java/org/nv95/openmanga/components/TopCropImageView.java deleted file mode 100755 index 0c69f516..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/TopCropImageView.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.nv95.openmanga.components; - -import android.content.Context; -import android.graphics.Matrix; -import android.util.AttributeSet; - -/** - * ImageView to display top-crop scale of an image view. - * https://gist.github.com/arriolac/3843346 - * - * @author Chris Arriola - */ -public class TopCropImageView extends android.support.v7.widget.AppCompatImageView { - - public TopCropImageView(Context context) { - this(context, null); - } - - public TopCropImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TopCropImageView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - private float mScale = -1f; - - protected void init(){ - setScaleType(ScaleType.MATRIX); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - recomputeImgMatrix(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - recomputeImgMatrix(); - } - - @Override - protected boolean setFrame(int l, int t, int r, int b) { - recomputeImgMatrix(); - return super.setFrame(l, t, r, b); - } - - private void recomputeImgMatrix() { - if (getDrawable() == null) { - return; - } - final Matrix matrix = getImageMatrix(); - - float scale; - final int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight(); - final int drawableWidth = getDrawable().getIntrinsicWidth(); - - scale = (float) viewWidth / (float) drawableWidth; - - if (scale != mScale) { - mScale = scale; - matrix.setScale(scale, scale); - setImageMatrix(matrix); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/TransitionDisplayer.java b/app/src/main/java/org/nv95/openmanga/components/TransitionDisplayer.java deleted file mode 100644 index 3571a467..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/TransitionDisplayer.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.nv95.openmanga.components; - -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import com.nostra13.universalimageloader.core.assist.LoadedFrom; -import com.nostra13.universalimageloader.core.display.BitmapDisplayer; -import com.nostra13.universalimageloader.core.imageaware.ImageAware; - -/** - * Created by nv95 on 27.08.16. - */ - -public class TransitionDisplayer implements BitmapDisplayer { - - @Override - public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) { - Drawable last = getDrawableFromView(imageAware.getWrappedView()); - if (last == null) { - imageAware.setImageBitmap(bitmap); - return; - } - TransitionDrawable td = new TransitionDrawable(new Drawable[]{ - last, - new BitmapDrawable(imageAware.getWrappedView().getResources(), bitmap) - }); - td.setCrossFadeEnabled(false); - imageAware.setImageDrawable(td); - td.startTransition(1000); - } - - @Nullable - private static Drawable getDrawableFromView(View v) { - if (v == null) { - return null; - } else if (v instanceof ImageView) { - return ((ImageView) v).getDrawable(); - } else { - return null; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/FileConverter.java b/app/src/main/java/org/nv95/openmanga/components/reader/FileConverter.java deleted file mode 100644 index fee003a6..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/FileConverter.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Handler; -import android.os.Message; -import android.util.Log; - -import org.nv95.openmanga.utils.FileLogger; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Created by admin on 17.08.16. - */ - -public class FileConverter implements Handler.Callback { - - private static final FileConverter instance = new FileConverter(); - - public static FileConverter getInstance() { - return instance; - } - - private final Handler mHandler; - private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); - - private FileConverter() { - mHandler = new Handler(this); - } - - @Override - public boolean handleMessage(Message msg) { - ConvertCallback callback = (ConvertCallback) ((WeakReference) msg.obj).get(); - if (callback != null) { - callback.onConvertDone(msg.what == 0); - } - return true; - } - - public void convertAsync(String filename, ConvertCallback callback) { - mExecutor.submit(new ConvertThread(filename, callback)); - } - - private class ConvertThread implements Runnable { - - private final String mFilename; - private final WeakReference mCallback; - - ConvertThread(String filename, ConvertCallback callback) { - mFilename = filename; - mCallback = new WeakReference<>(callback); - } - - @Override - public void run() { - int result = -1; - FileOutputStream out = null; - Bitmap bitmap = null; - try { - bitmap = BitmapFactory.decodeFile(mFilename); - out = new FileOutputStream(mFilename); - bitmap.compress(Bitmap.CompressFormat.JPEG, 95, out); - result = 0; - Log.d("CONVERT", "SUCCESS"); - } catch (Exception e) { - FileLogger.getInstance().report("CONVERT", e); - } finally { - if (bitmap != null) { - bitmap.recycle(); - } - if (out != null) { - try { - out.close(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } - Message msg = new Message(); - msg.obj = mCallback; - msg.what = result; - mHandler.sendMessage(msg); - } - } - - public interface ConvertCallback { - void onConvertDone(boolean success); - } - - public static class ConvertException extends Exception { - - } - } diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/MangaReader.java b/app/src/main/java/org/nv95/openmanga/components/reader/MangaReader.java deleted file mode 100644 index 0fbbc321..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/MangaReader.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.content.Context; - -import org.nv95.openmanga.components.reader.recyclerpager.RecyclerViewPager; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.utils.InternalLinkMovement; - -import java.util.List; - -/** - * Created by admin on 28.07.17. - */ - -public interface MangaReader { - - void applyConfig(boolean vertical, boolean reverse, boolean sticky, boolean showNumbers); - boolean scrollToNext(boolean animate); - boolean scrollToPrevious(boolean animate); - int getCurrentPosition(); - void scrollToPosition(int position); - void setTapNavs(boolean val); - - void addOnPageChangedListener(RecyclerViewPager.OnPageChangedListener listener); - - void setOnOverScrollListener(OnOverScrollListener listener); - - boolean isReversed(); - - int getItemCount(); - - void initAdapter(Context context, InternalLinkMovement.OnLinkClickListener linkListener); - - PageLoader getLoader(); - - void notifyDataSetChanged(); - - PageWrapper getItem(int position); - - void setScaleMode(int scaleMode); - - void reload(int position); - - void setPages(List mangaPages); - - void finish(); - - List getPages(); -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/OnOverScrollListener.java b/app/src/main/java/org/nv95/openmanga/components/reader/OnOverScrollListener.java deleted file mode 100644 index 0003c1ac..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/OnOverScrollListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.nv95.openmanga.components.reader; - -/** - * Created by nv95 on 18.11.16. - */ - -public interface OnOverScrollListener { - - int LEFT = 0; - int RIGHT = 1; - int TOP = 2; - int BOTTOM = 3; - - void onOverScrollFlying(int direction, int distance); - boolean onOverScrollFinished(int direction, int distance); - void onOverScrollStarted(int direction); - void onOverScrolled(int direction); -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/OverscrollDetector.java b/app/src/main/java/org/nv95/openmanga/components/reader/OverscrollDetector.java deleted file mode 100644 index 767975c0..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/OverscrollDetector.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.annotation.SuppressLint; -import android.graphics.Point; -import android.graphics.PointF; -import android.view.MotionEvent; -import android.view.View; - -import org.nv95.openmanga.utils.DecartUtils; - -/** - * Created by unravel22 on 16.09.17. - */ - -public class OverscrollDetector implements View.OnTouchListener { - - private final OnOverScrollListener mListener; - private boolean mCanScrollLeft, mCanScrollRight, mHandleHorizontal; - private boolean mCanScrollTop, mCanScrollBottom, mHandleVertical; - private PointF mStartPoint; - - public OverscrollDetector(OnOverScrollListener onOverScrollListener) { - this.mListener = onOverScrollListener; - } - - public void setDirections(boolean handleVertical, boolean handleHorizontal) { - mHandleHorizontal = handleHorizontal; - mHandleVertical = handleVertical; - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - if (motionEvent.getPointerCount() != 1) return false; - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - mCanScrollTop = mHandleVertical && !view.canScrollVertically(-1); - mCanScrollBottom = mHandleVertical && !view.canScrollVertically(1); - mCanScrollLeft = mHandleHorizontal && !view.canScrollHorizontally(-1); - mCanScrollRight = mHandleHorizontal && !view.canScrollHorizontally(1); - if (mCanScrollTop) mListener.onOverScrollStarted(OnOverScrollListener.TOP); - else if (mCanScrollBottom) - mListener.onOverScrollStarted(OnOverScrollListener.BOTTOM); - else if (mCanScrollLeft) mListener.onOverScrollStarted(OnOverScrollListener.LEFT); - else if (mCanScrollRight) mListener.onOverScrollStarted(OnOverScrollListener.RIGHT); - else return false; - mStartPoint = new PointF(motionEvent.getX(), motionEvent.getY()); - return true; - case MotionEvent.ACTION_MOVE: - int dY = (int) (mStartPoint.y - motionEvent.getY()); - int dX = (int) (mStartPoint.x - motionEvent.getX()); - if (mCanScrollTop && dY < 0) - mListener.onOverScrollFlying(OnOverScrollListener.TOP, -dY); - else if (mCanScrollBottom && dY > 0) - mListener.onOverScrollFlying(OnOverScrollListener.BOTTOM, dY); - else if (mCanScrollLeft && dX < 0) - mListener.onOverScrollFlying(OnOverScrollListener.LEFT, -dX); - else if (mCanScrollRight && dX > 0) - mListener.onOverScrollFlying(OnOverScrollListener.RIGHT, dX); - else return false; - return true; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - dY = (int) (mStartPoint.y - motionEvent.getY()); - dX = (int) (mStartPoint.x - motionEvent.getX()); - - if (mCanScrollTop && dY < 0 && mListener.onOverScrollFinished(OnOverScrollListener.TOP, -dY)) - mListener.onOverScrolled(OnOverScrollListener.TOP); - else if (mCanScrollBottom && dY > 0 && mListener.onOverScrollFinished(OnOverScrollListener.BOTTOM, dY)) - mListener.onOverScrolled(OnOverScrollListener.BOTTOM); - else if (mCanScrollLeft && dX < 0 && mListener.onOverScrollFinished(OnOverScrollListener.LEFT, -dX)) - mListener.onOverScrolled(OnOverScrollListener.LEFT); - else if (mCanScrollRight && dX > 0 && mListener.onOverScrollFinished(OnOverScrollListener.RIGHT, dX)) - mListener.onOverScrolled(OnOverScrollListener.RIGHT); - else return false; - return true; - } - return false; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/PageLoadListener.java b/app/src/main/java/org/nv95/openmanga/components/reader/PageLoadListener.java deleted file mode 100644 index 9eb42909..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/PageLoadListener.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.nv95.openmanga.components.reader; - -/** - * Created by nv95 on 16.11.16. - */ - -public interface PageLoadListener { - - void onLoadingStarted(PageWrapper page, boolean shadow); - void onProgressUpdated(PageWrapper page, boolean shadow, int percent); - void onLoadingComplete(PageWrapper page, boolean shadow); - void onLoadingFail(PageWrapper page, boolean shadow); - void onLoadingCancelled(PageWrapper page, boolean shadow); -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/PageLoadTask.java b/app/src/main/java/org/nv95/openmanga/components/reader/PageLoadTask.java deleted file mode 100644 index 1ff373be..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/PageLoadTask.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.content.Context; -import android.os.AsyncTask; -import android.support.annotation.Nullable; - -import com.nostra13.universalimageloader.cache.disc.DiskCache; -import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.utils.DiskCacheUtils; -import com.nostra13.universalimageloader.utils.IoUtils; - -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.utils.NoSSLv3SocketFactory; - -import java.io.File; -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.net.HttpURLConnection; - -import javax.net.ssl.HttpsURLConnection; - -import info.guardianproject.netcipher.NetCipher; - -/** - * Created by nv95 on 16.11.16. - */ - -public class PageLoadTask extends AsyncTask { - - private final PageWrapper mPageWrapper; - @Nullable - private PageLoadListener mListener; - private boolean mIsShadow; - private final MangaProvider mProvider; - - public PageLoadTask(Context context, PageWrapper pageWrapper, @Nullable PageLoadListener listener) { - mPageWrapper = pageWrapper; - mListener = listener; - mProvider = MangaProviderManager.instanceProvider(context, mPageWrapper.page.provider); - } - - public WeakReference start(boolean shadow) { - mIsShadow = shadow; - executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, shadow ? 0 : 2); - return new WeakReference<>(this); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mPageWrapper.mState = PageWrapper.STATE_PROGRESS; - if (mListener != null) { - mListener.onLoadingStarted(mPageWrapper, mIsShadow); - } - } - - @Override - protected Object doInBackground(Integer... params) { - try { - int priority = params.length > 0 ? params[0] : 0; - Thread.currentThread().setPriority(Thread.NORM_PRIORITY + priority); - if (mPageWrapper.page.path.startsWith("/")) { - return mPageWrapper.page.path; - } - String url = mProvider.getPageImage(mPageWrapper.page); - DiskCache cache = ImageLoader.getInstance().getDiskCache(); - File file = DiskCacheUtils.findInCache(url, cache); - if (file != null) { - return file.getAbsolutePath(); - } - HttpURLConnection connection = NetCipher.getHttpURLConnection(url); - if (connection instanceof HttpsURLConnection) { - ((HttpsURLConnection) connection).setSSLSocketFactory(NoSSLv3SocketFactory.getInstance()); - } - connection.connect(); - final int contentLength = connection.getContentLength(); - InputStream is = connection.getInputStream(); - - cache.save(url, is, new IoUtils.CopyListener() { - @Override - public boolean onBytesCopied(int current, int total) { //total is incorrect - int percent = contentLength > 0 ? current * 100 / contentLength : 0; - if (contentLength > 0) { - publishProgress(percent); - } - return !isCancelled() || percent > 80; - } - }); - file = DiskCacheUtils.findInCache(url, cache); - if (file != null) { - return file.getAbsolutePath(); - } else { - return null; - } - } catch (Exception e) { - return e; - } - } - - @Override - protected void onProgressUpdate(Integer... values) { - if (mListener != null) { - mListener.onProgressUpdated(mPageWrapper, mIsShadow, values[0]); - } - } - - @Override - protected void onCancelled(Object o) { - super.onCancelled(o); - if (o != null && o instanceof String) { - mPageWrapper.mState = PageWrapper.STATE_LOADED; - mPageWrapper.mFilename = (String) o; - if (mListener != null) { - mListener.onLoadingComplete(mPageWrapper, mIsShadow); - } - } else { - mPageWrapper.mState = PageWrapper.STATE_QUEUED; - if (mListener != null) { - mListener.onLoadingCancelled(mPageWrapper, mIsShadow); - } - } - } - - @Override - protected void onPostExecute(Object o) { - super.onPostExecute(o); - if (o == null) { - mPageWrapper.mState = PageWrapper.STATE_QUEUED; - if (mListener != null) { - mListener.onLoadingFail(mPageWrapper, mIsShadow); - } - } else if (o instanceof String) { - mPageWrapper.mState = PageWrapper.STATE_LOADED; - mPageWrapper.mFilename = (String) o; - if (mListener != null) { - mListener.onLoadingComplete(mPageWrapper, mIsShadow); - } - } else if (o instanceof Exception) { - mPageWrapper.mState = PageWrapper.STATE_QUEUED; - mPageWrapper.mError = (Exception) o; - if (mListener != null) { - mListener.onLoadingFail(mPageWrapper, mIsShadow); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/PageLoader.java b/app/src/main/java/org/nv95/openmanga/components/reader/PageLoader.java deleted file mode 100644 index 4ef1c2cf..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/PageLoader.java +++ /dev/null @@ -1,217 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.content.Context; -import android.os.AsyncTask; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import org.nv95.openmanga.items.MangaPage; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -/** - * Created by nv95 on 16.11.16. - */ - -public class PageLoader implements PageLoadListener { - - private static final int MAX_SHADOW_THREADS = 2; - - private boolean mEnabled; - private boolean mPreloadEnabled; - private final HashSet mListeners; - private final ArrayList mWrappers; - private int mActiveLoads; - private int mShadowLoads; - private final Context mContext; - - - public PageLoader(Context context) { - mContext = context; - mListeners = new HashSet<>(5); - mWrappers = new ArrayList<>(); - mActiveLoads = 0; - mShadowLoads = 0; - mEnabled = true; - mPreloadEnabled = false; - } - - public void setPages(@NonNull List pages) { - mWrappers.clear(); - for (int i=0;i getWrappersList() { - return mWrappers; - } - - @Nullable - public PageWrapper requestPage(int pos) { - if (!mEnabled || pos < 0 || pos >= mWrappers.size()) { - Log.w("PGL", "#null " + pos + " of " + mWrappers.size()); - return null; - } - PageWrapper wrapper = mWrappers.get(pos); - if (wrapper.mState == PageWrapper.STATE_QUEUED) { - Log.d("PGL", "#current " + wrapper.toString()); - wrapper.mTaskRef = new PageLoadTask(mContext, wrapper, this).start(false); - } - return wrapper; - } - - public void shadowLoad(int pos) { - if (!mEnabled || !mPreloadEnabled || pos < 0 || pos >= mWrappers.size()) { - return; - } - PageWrapper wrapper = null; - for (int i=pos;i= mWrappers.size()) { - return; - } - PageWrapper wrapper = mWrappers.get(pos); - PageLoadTask task = wrapper.getLoadTask(); - if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { - task.cancel(false); - } - } - - @Override - public void onLoadingStarted(PageWrapper page, boolean shadow) { - Log.d("PGL", "Started " + page.toString() + (shadow ? "#" : "%")); - if (shadow) { - mShadowLoads++; - } else { - mActiveLoads++; - } - for (PageLoadListener o : mListeners) { - o.onLoadingStarted(page, shadow); - } - } - - @Override - public void onProgressUpdated(PageWrapper page, boolean shadow, int percent) { - for (PageLoadListener o : mListeners) { - o.onProgressUpdated(page, shadow, percent); - } - } - - @Override - public void onLoadingComplete(PageWrapper page, boolean shadow) { - Log.d("PGL", "Complete " + page.toString() + (shadow ? "#" : "%")); - for (PageLoadListener o : mListeners) { - o.onLoadingComplete(page, shadow); - } - if (shadow) { - mShadowLoads--; - if (mShadowLoads <= MAX_SHADOW_THREADS) { - shadowLoad(page.position + 1); - } - } else { - mActiveLoads--; - if (mActiveLoads <= 0) { - shadowLoad(page.position + 1); - } - } - } - - @Override - public void onLoadingFail(PageWrapper page, boolean shadow) { - Log.d("PGL", "Fail " + page.toString() + (shadow ? "#" : "%")); - for (PageLoadListener o : mListeners) { - o.onLoadingFail(page, shadow); - } - if (shadow) { - mShadowLoads--; - if (mShadowLoads <= MAX_SHADOW_THREADS) { - shadowLoad(page.position + 1); - } - } else { - mActiveLoads--; - } - } - - @Override - public void onLoadingCancelled(PageWrapper page, boolean shadow) { - Log.d("PGL", "Cancelled " + page.toString() + (shadow ? "#" : "%")); - for (PageLoadListener o : mListeners) { - o.onLoadingCancelled(page, shadow); - } - if (shadow) { - mShadowLoads--; - } else { - mActiveLoads--; - } - } - - public void addListener(PageLoadListener listener) { - Log.d("LIST", "Added; total: " + mListeners.size()); - mListeners.add(listener); - } - - public void removeListener(PageLoadListener listener) { - Log.d("LIST", "Removed; total: " + mListeners.size()); - mListeners.remove(listener); - } - - public void clearListeners() { - Log.d("LIST", "Cleared!"); - mListeners.clear(); - } - - public void setEnabled(boolean b) { - mEnabled = b; - } - - public void setPreloadEnabled(boolean b) { - mPreloadEnabled = b; - } - - public void cancelAll() { - PageLoadTask task; - for (PageWrapper o : mWrappers) { - task = o.getLoadTask(); - if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { - task.cancel(true); - } - } - } - - public void drop(int position) { - if (position < 0 || position >= mWrappers.size()) { - return; - } - PageWrapper wrapper = mWrappers.get(position); - PageLoadTask task = wrapper.getLoadTask(); - if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { - task.cancel(false); - } else { - String filename = wrapper.getFilename(); - if (filename != null) { - try { - new File(filename).delete(); - } catch (Exception ignored) { - - } - } - } - wrapper.mState = PageWrapper.STATE_QUEUED; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/PageWrapper.java b/app/src/main/java/org/nv95/openmanga/components/reader/PageWrapper.java deleted file mode 100644 index 2575cf5f..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/PageWrapper.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.support.annotation.Nullable; - -import org.nv95.openmanga.items.MangaPage; - -import java.lang.ref.WeakReference; - -/** - * Created by nv95 on 15.11.16. - */ - -public class PageWrapper { - - public static final int STATE_QUEUED = 0; - public static final int STATE_PROGRESS = 1; - public static final int STATE_LOADED = 2; - - public final MangaPage page; - public final int position; - - int mState; - @Nullable - String mFilename; - @Nullable - Exception mError; - @Nullable - WeakReference mTaskRef; - private boolean mConverted; - - public PageWrapper(MangaPage page, int position) { - this.page = page; - this.position = position; - if (page.path.startsWith("/")) { - mState = STATE_LOADED; - mFilename = page.path; - } else { - mState = STATE_QUEUED; - mFilename = null; - } - mError = null; - mTaskRef = null; - mConverted = false; - } - - @Nullable - public String getFilename() { - return mFilename; - } - - @Nullable - public Exception getError() { - return mError; - } - - public int getState() { - return mState; - } - - public boolean isLoaded() { - return mState == STATE_LOADED; - } - - @Nullable - PageLoadTask getLoadTask() { - return mTaskRef == null ? null : mTaskRef.get(); - } - - public void setConverted() { - mConverted = true; - } - - public boolean isConverted() { - return mConverted; - } - - @Override - public boolean equals(Object obj) { - return obj != null && ( - (obj instanceof PageWrapper && ((PageWrapper) obj).page.equals(this.page)) - || (obj instanceof MangaPage && obj.equals(this.page)) - ); - } - - //only debug info - @Override - public String toString() { - return "page " + position + " id: " + page.id + " filename: " + mFilename; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/ReaderAdapter.java b/app/src/main/java/org/nv95/openmanga/components/reader/ReaderAdapter.java deleted file mode 100644 index 6b26b6a1..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/ReaderAdapter.java +++ /dev/null @@ -1,263 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AlphaAnimation; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.davemorrissey.labs.subscaleview.ImageSource; -import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.ReaderConfig; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.InternalLinkMovement; -import org.nv95.openmanga.utils.SsivUtils; - -import java.util.List; -import java.util.Locale; - -/** - * Created by nv95 on 15.11.16. - */ - -public class ReaderAdapter extends RecyclerView.Adapter { - - private final PageLoader mLoader; - private final InternalLinkMovement mMovement; - - public ReaderAdapter(Context context, InternalLinkMovement.OnLinkClickListener linkListener) { - mLoader = new PageLoader(context); - mMovement = new InternalLinkMovement(linkListener); - setHasStableIds(true); - } - - public void setPages(@NonNull List pages) { - mLoader.setPages(pages); - } - - public void setScaleMode(int mode) { - PageHolder.scaleMode = mode; - } - - public PageLoader getLoader() { - return mLoader; - } - - @Override - public PageHolder onCreateViewHolder(ViewGroup parent, int viewType) { - PageHolder holder = new PageHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_page, parent, false)); - holder.textView.setMovementMethod(mMovement); - mLoader.addListener(holder); - return holder; - } - - @Override - public void onBindViewHolder(PageHolder holder, int position) { - holder.reset(); - PageWrapper wrapper = mLoader.requestPage(position); - if (wrapper != null) { - switch (wrapper.getState()) { - case PageWrapper.STATE_PROGRESS: - holder.onLoadingStarted(wrapper, false); - break; - case PageWrapper.STATE_LOADED: - if (wrapper.mFilename != null) { - holder.onLoadingComplete(wrapper, false); - } else { - holder.onLoadingFail(wrapper, false); - } - } - } - } - - public void finish() { - mLoader.clearListeners(); - mLoader.cancelAll(); - mLoader.setEnabled(false); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public void onViewRecycled(PageHolder holder) { - holder.reset(); - super.onViewRecycled(holder); - } - - @Override - public int getItemCount() { - return mLoader.getWrappersList().size(); - } - - public PageWrapper getItem(int position) { - return mLoader.getWrappersList().get(position); - } - - public void reload(int position) { - mLoader.drop(position); - notifyItemChanged(position); - } - - static class PageHolder extends RecyclerView.ViewHolder implements PageLoadListener, - SubsamplingScaleImageView.OnImageEventListener, FileConverter.ConvertCallback { - - static int scaleMode; - - final ProgressBar progressBar; - final SubsamplingScaleImageView ssiv; - final TextView textView; - @Nullable - PageWrapper pageWrapper; - - PageHolder(View itemView) { - super(itemView); - progressBar = itemView.findViewById(R.id.progressBar); - ssiv = itemView.findViewById(R.id.ssiv); - textView = itemView.findViewById(R.id.textView_holder); - ssiv.setParallelLoadingEnabled(true); - ssiv.setOnImageEventListener(this); - } - - void reset() { - pageWrapper = null; - ssiv.recycle(); - textView.setVisibility(View.GONE); - } - - @Override - public void onLoadingStarted(PageWrapper page, boolean shadow) { - if (page.position == getAdapterPosition()) { - textView.setText(textView.getContext().getString(R.string.loading).toUpperCase(Locale.getDefault())); - textView.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.VISIBLE); - progressBar.setIndeterminate(true); - } - } - - @Override - public void onProgressUpdated(PageWrapper page, boolean shadow, int percent) { - if (page.position == getAdapterPosition()) { - progressBar.setIndeterminate(false); - progressBar.setProgress(percent); - textView.setText(textView.getContext().getString(R.string.loading_percent, percent).toUpperCase(Locale.getDefault())); - progressBar.setVisibility(View.VISIBLE); - textView.setVisibility(View.VISIBLE); - } - } - - @Override - public void onLoadingComplete(PageWrapper page, boolean shadow) { - if (page.position == getAdapterPosition()) { - textView.setText(textView.getContext().getString(R.string.wait).toUpperCase(Locale.getDefault())); - textView.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.VISIBLE); - progressBar.setIndeterminate(true); - pageWrapper = page; - //noinspection ConstantConditions - ssiv.setImage(ImageSource.uri(page.getFilename()).tilingEnabled()); - } - } - - @Override - public void onLoadingFail(PageWrapper page, boolean shadow) { - if (page.position == getAdapterPosition()) { - onImageLoadError(page.getError()); - } - } - - @Override - public void onLoadingCancelled(PageWrapper page, boolean shadow) { - if (page.position == getAdapterPosition()) { - progressBar.setVisibility(View.GONE); - textView.setVisibility(View.GONE); - } - } - - @Override - public void onReady() { - - } - - @Override - public void onImageLoaded() { - progressBar.setVisibility(View.GONE); - textView.setVisibility(View.GONE); - switch (scaleMode) { - case ReaderConfig.SCALE_FIT: - SsivUtils.setScaleFit(ssiv); - break; - case ReaderConfig.SCALE_FIT_W: - SsivUtils.setScaleWidthTop(ssiv); - break; - case ReaderConfig.SCALE_FIT_H: - SsivUtils.setScaleHeightLeft(ssiv); - break; - case ReaderConfig.SCALE_FIT_H_REV: - SsivUtils.setScaleHeightRight(ssiv); - break; - case ReaderConfig.SCALE_ZOOM: - SsivUtils.setScaleZoomSrc(ssiv); - break; - } - AlphaAnimation aa = new AlphaAnimation(0.f, 1.f); - aa.setDuration(100); - aa.setRepeatCount(0); - ssiv.startAnimation(aa); - } - - @Override - public void onPreviewLoadError(Exception e) { - - } - - @Override - public void onImageLoadError(Exception e) { - if (pageWrapper != null && !pageWrapper.isConverted() && pageWrapper.mFilename != null) { - FileConverter.getInstance().convertAsync(pageWrapper.getFilename(), this); - return; - } - progressBar.setVisibility(View.GONE); - textView.setVisibility(View.VISIBLE); - textView.setText( - AppHelper.fromHtml(textView.getContext().getString( - R.string.error_loadimage_html, - FileLogger.getInstance().getFailMessage(textView.getContext(), e) - ), true) - ); - } - - @Override - public void onTileLoadError(Exception e) { - - } - - @Override - public void onPreviewReleased() { - - } - - @Override - public void onConvertDone(boolean success) { - if (pageWrapper != null) { - pageWrapper.setConverted(); - if (success) { - onLoadingComplete(pageWrapper, false); - } else { - onImageLoadError(new FileConverter.ConvertException()); - } - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/StandardMangaReader.java b/app/src/main/java/org/nv95/openmanga/components/reader/StandardMangaReader.java deleted file mode 100644 index 255b2a16..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/StandardMangaReader.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.nv95.openmanga.components.reader; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import org.nv95.openmanga.components.reader.recyclerpager.PreCachingLayoutManager; -import org.nv95.openmanga.components.reader.recyclerpager.RecyclerViewPager; -import org.nv95.openmanga.components.reader.recyclerpager.overscroll.HorizontalOverScrollBounceEffectDecorator; -import org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollDecor; -import org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollState; -import org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollStateListener; -import org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollUpdateListener; -import org.nv95.openmanga.components.reader.recyclerpager.overscroll.RecyclerViewOverScrollDecorAdapter; -import org.nv95.openmanga.components.reader.recyclerpager.overscroll.VerticalOverScrollBounceEffectDecorator; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.utils.InternalLinkMovement; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by nv95 on 15.11.16. - */ - -public class StandardMangaReader extends RecyclerViewPager implements MangaReader, - IOverScrollUpdateListener, IOverScrollStateListener { - - private static final float OVERSCROLL_THRESHOLD = 80; - - @Nullable - private IOverScrollDecor mOverScrollDecor; - @Nullable - private PreCachingLayoutManager mLayoutManager; - @Nullable - private OnOverScrollListener mOverScrollListener; - private int mOverScrollDirection; - private float mOverScrollFactor; - private boolean mNavEnabled; - private float[] mNavPoint; - - private ReaderAdapter mAdapter; - - public StandardMangaReader(Context context) { - super(context); - init(context); - } - - public StandardMangaReader(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(context); - } - - public StandardMangaReader(Context context, @Nullable AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context); - } - - private int getSide(float x) { - final int w = getResources().getDisplayMetrics().widthPixels; - final float sw = w / 3.f; - if (x < sw) { - return -1; - } else if (x > (sw * 2.f)) { - return 1; - } else { - return 0; - } - } - - private void init(Context context) { - mLayoutManager = null; - mNavEnabled = false; - mNavPoint = new float[2]; - addOnItemTouchListener(new OnItemTouchListener() { - @Override - public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { - if (mNavEnabled && rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mNavPoint[0] = ev.getX(); - if (getSide(mNavPoint[0]) == 0) { - return false; - } - mNavPoint[1] = ev.getY(); - return true; - case MotionEvent.ACTION_UP: - float dx = mNavPoint[0] - ev.getX(); - float dy = mNavPoint[1] - ev.getY(); - double delta = Math.sqrt(dx * dx + dy * dy); - if (delta < 10) { - int d = getSide(mNavPoint[0]) * (isReversed() ? -1 : 1); - smoothScrollToPosition(getCurrentPosition() + d); - } - break; - } - } - return false; - } - - @Override - public void onTouchEvent(RecyclerView rv, MotionEvent e) { - - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - - } - }); - } - - @Override - public void applyConfig(boolean vertical, boolean reverse, boolean sticky, boolean showNumbers) { - int oldPos = getCurrentPosition(); - setLayoutManager(mLayoutManager = new PreCachingLayoutManager( - getContext(), - vertical ? PreCachingLayoutManager.VERTICAL : PreCachingLayoutManager.HORIZONTAL, - reverse - )); - setSinglePageFling(sticky); - setSticky(sticky); - if (mOverScrollDecor != null) { - mOverScrollDecor.detach(); - } - mOverScrollDecor = vertical ? - new VerticalOverScrollBounceEffectDecorator(new RecyclerViewOverScrollDecorAdapter(this)) - : new HorizontalOverScrollBounceEffectDecorator(new RecyclerViewOverScrollDecorAdapter(this)); - mOverScrollDecor.setOverScrollUpdateListener(this); - mOverScrollDecor.setOverScrollStateListener(this); - if (oldPos != RecyclerView.NO_POSITION) { - scrollToPosition(oldPos); - } - } - - public boolean isVertical() { - return mLayoutManager != null && mLayoutManager.getOrientation() == PreCachingLayoutManager.VERTICAL; - } - - public boolean isReversed() { - return mLayoutManager != null && mLayoutManager.getReverseLayout(); - } - - @Override - public void initAdapter(Context context, InternalLinkMovement.OnLinkClickListener linkListener) { - mAdapter = new ReaderAdapter(context, linkListener); - setAdapter(mAdapter); - } - - @Override - public PageLoader getLoader() { - return mAdapter.getLoader(); - } - - @Override - public void notifyDataSetChanged() { - mAdapter.notifyDataSetChanged(); - } - - @Override - public PageWrapper getItem(int position) { - return mAdapter.getItem(position); - } - - @Override - public void setScaleMode(int scaleMode) { - mAdapter.setScaleMode(scaleMode); - } - - @Override - public void reload(int position) { - mAdapter.reload(position); - } - - @Override - public void setPages(List mangaPages) { - mAdapter.setPages(mangaPages); - } - - @Override - public List getPages() { - List wrappers = mAdapter.getLoader().getWrappersList(); - ArrayList pages = new ArrayList<>(wrappers.size()); - for (PageWrapper o : wrappers) { - pages.add(o.page); - } - return pages; - } - - @Override - public void finish() { - mAdapter.finish(); - } - - public void setOnOverScrollListener(OnOverScrollListener listener) { - mOverScrollListener = listener; - } - - @Override - public void setTapNavs(boolean val) { - mNavEnabled = val; - } - - void scrollToPosition(int pos, boolean animate) { - if (animate) { - smoothScrollToPosition(pos); - } else { - scrollToPosition(pos); - } - } - - @Override - public boolean scrollToNext(boolean animate) { - int pos = getCurrentPosition(); - if (pos < getItemCount() - 1) { - scrollToPosition(pos + 1, animate); - return true; - } else { - return false; - } - } - - @Override - public boolean scrollToPrevious(boolean animate) { - int pos = getCurrentPosition(); - if (pos > 0) { - scrollToPosition(pos - 1, animate); - return true; - } else { - return false; - } - } - - @Override - public void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset) { - mOverScrollFactor = offset; - if (mOverScrollListener != null) { - mOverScrollListener.onOverScrollFlying(mOverScrollDirection, Math.abs((int)offset)); - } - } - - @Override - public void onOverScrollStateChange(IOverScrollDecor decor, int oldState, int newState) { - switch (newState) { - case IOverScrollState.STATE_IDLE: - if (mOverScrollListener != null) { - mOverScrollListener.onOverScrollFinished(mOverScrollDirection, 0); - } - // No over-scroll is in effect. - break; - case IOverScrollState.STATE_DRAG_START_SIDE: - // Dragging started at the left-end. - mOverScrollFactor = 0; - mOverScrollDirection = isVertical() ? OnOverScrollListener.TOP : OnOverScrollListener.LEFT; - if (mOverScrollListener != null) { - mOverScrollListener.onOverScrollStarted(mOverScrollDirection); - } - break; - case IOverScrollState.STATE_DRAG_END_SIDE: - // Dragging started at the right-end. - mOverScrollFactor = 0; - mOverScrollDirection = isVertical() ? OnOverScrollListener.BOTTOM : OnOverScrollListener.RIGHT; - if (mOverScrollListener != null) { - mOverScrollListener.onOverScrollStarted(mOverScrollDirection); - } - break; - case IOverScrollState.STATE_BOUNCE_BACK: - if (oldState == IOverScrollState.STATE_DRAG_START_SIDE || oldState == IOverScrollState.STATE_DRAG_END_SIDE) { - if (mOverScrollListener != null) { - if (Math.abs(mOverScrollFactor) < OVERSCROLL_THRESHOLD) { - mOverScrollListener.onOverScrollFinished(mOverScrollDirection, 0); - } else { - mOverScrollListener.onOverScrolled(mOverScrollDirection); - } - } - } - break; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/PreCachingLayoutManager.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/PreCachingLayoutManager.java deleted file mode 100644 index db102e95..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/PreCachingLayoutManager.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager; - -import android.content.Context; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; - -/** - * Created by nv95 on 17.11.16. - */ - -public class PreCachingLayoutManager extends LinearLayoutManager { - - private final int mExtraSize; - - public PreCachingLayoutManager(Context context, int orientation, boolean reverseLayout) { - super(context, orientation, reverseLayout); - mExtraSize = orientation == VERTICAL ? - context.getResources().getDisplayMetrics().heightPixels : - context.getResources().getDisplayMetrics().widthPixels; - } - - @Override - protected int getExtraLayoutSpace(RecyclerView.State state) { - return mExtraSize; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/RecyclerViewPager.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/RecyclerViewPager.java deleted file mode 100644 index f0c2f130..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/RecyclerViewPager.java +++ /dev/null @@ -1,480 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager; - -import android.content.Context; -import android.graphics.PointF; -import android.os.Build; -import android.os.Parcelable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.LinearSmoothScroller; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewTreeObserver; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -/** - * Source from lsjwzh/RecyclerViewPager - * https://github.com/lsjwzh/RecyclerViewPager - * @author Green - */ -public class RecyclerViewPager extends RecyclerView { - - private RecyclerView.Adapter mViewPagerAdapter; - private float mTriggerOffset = 0.25f; - private float mFlingFactor = 0.15f; - private float mTouchSpan; - private List mOnPageChangedListeners; - private int mSmoothScrollTargetPosition = -1; - private int mPositionBeforeScroll = -1; - - private boolean mSinglePageFling; - private boolean mSticky = true; - - boolean mNeedAdjust; - int mFisrtLeftWhenDragging; - int mFirstTopWhenDragging; - View mCurView; - int mMaxLeftWhenDragging = Integer.MIN_VALUE; - int mMinLeftWhenDragging = Integer.MAX_VALUE; - int mMaxTopWhenDragging = Integer.MIN_VALUE; - int mMinTopWhenDragging = Integer.MAX_VALUE; - private int mPositionOnTouchDown = -1; - private boolean mHasCalledOnPageChanged = true; - private boolean reverseLayout = false; - - public RecyclerViewPager(Context context) { - this(context, null); - } - - public RecyclerViewPager(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public RecyclerViewPager(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - //initAttrs(context, attrs, defStyle); - setNestedScrollingEnabled(false); - } - - public void setFlingFactor(float flingFactor) { - mFlingFactor = flingFactor; - } - - public float getFlingFactor() { - return mFlingFactor; - } - - public void setTriggerOffset(float triggerOffset) { - mTriggerOffset = triggerOffset; - } - - public float getTriggerOffset() { - return mTriggerOffset; - } - - public void setSinglePageFling(boolean singlePageFling) { - mSinglePageFling = singlePageFling; - } - - public boolean isSinglePageFling() { - return mSinglePageFling; - } - - public void setSticky(boolean sticky) { - mSticky = sticky; - } - - public boolean isSticky() { - return mSticky; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - try { - Field fLayoutState = state.getClass().getDeclaredField("mLayoutState"); - fLayoutState.setAccessible(true); - Object layoutState = fLayoutState.get(state); - Field fAnchorOffset = layoutState.getClass().getDeclaredField("mAnchorOffset"); - Field fAnchorPosition = layoutState.getClass().getDeclaredField("mAnchorPosition"); - fAnchorPosition.setAccessible(true); - fAnchorOffset.setAccessible(true); - if (fAnchorOffset.getInt(layoutState) > 0) { - fAnchorPosition.set(layoutState, fAnchorPosition.getInt(layoutState) - 1); - } else if (fAnchorOffset.getInt(layoutState) < 0) { - fAnchorPosition.set(layoutState, fAnchorPosition.getInt(layoutState) + 1); - } - fAnchorOffset.setInt(layoutState, 0); - } catch (Throwable e) { - e.printStackTrace(); - } - super.onRestoreInstanceState(state); - } - - @Override - public void setAdapter(Adapter adapter) { - mViewPagerAdapter = adapter; - super.setAdapter(mViewPagerAdapter); - } - - @Override - public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) { - mViewPagerAdapter = adapter; - super.swapAdapter(mViewPagerAdapter, removeAndRecycleExistingViews); - } - - @Override - public void setLayoutManager(LayoutManager layout) { - super.setLayoutManager(layout); - - if (layout instanceof LinearLayoutManager) { - reverseLayout = ((LinearLayoutManager) layout).getReverseLayout(); - } - } - - @Override - public boolean fling(int velocityX, int velocityY) { - if (!mSticky) { - return super.fling(velocityX, velocityY); - } - boolean flinging = super.fling((int) (velocityX * mFlingFactor), (int) (velocityY * mFlingFactor)); - if (flinging) { - if (getLayoutManager().canScrollHorizontally()) { - adjustPositionX(velocityX); - } else { - adjustPositionY(velocityY); - } - } - return flinging; - } - - @Override - public void smoothScrollToPosition(int position) { - mPositionBeforeScroll = getCurrentPosition(); - mSmoothScrollTargetPosition = position; - if (getLayoutManager() != null && getLayoutManager() instanceof LinearLayoutManager) { - // exclude item decoration - LinearSmoothScroller linearSmoothScroller = - new LinearSmoothScroller(getContext()) { - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - if (getLayoutManager() == null) { - return null; - } - return ((LinearLayoutManager) getLayoutManager()) - .computeScrollVectorForPosition(targetPosition); - } - - @Override - protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { - if (getLayoutManager() == null) { - return; - } - int dx = calculateDxToMakeVisible(targetView, - getHorizontalSnapPreference()); - int dy = calculateDyToMakeVisible(targetView, - getVerticalSnapPreference()); - if (dx > 0) { - dx = dx - getLayoutManager() - .getLeftDecorationWidth(targetView); - } else { - dx = dx + getLayoutManager() - .getRightDecorationWidth(targetView); - } - if (dy > 0) { - dy = dy - getLayoutManager() - .getTopDecorationHeight(targetView); - } else { - dy = dy + getLayoutManager() - .getBottomDecorationHeight(targetView); - } - final int distance = (int) Math.sqrt(dx * dx + dy * dy); - final int time = calculateTimeForDeceleration(distance); - if (time > 0) { - action.update(-dx, -dy, time, mDecelerateInterpolator); - } - } - }; - linearSmoothScroller.setTargetPosition(position); - if (position == RecyclerView.NO_POSITION) { - return; - } - getLayoutManager().startSmoothScroll(linearSmoothScroller); - } else { - super.smoothScrollToPosition(position); - } - } - - @Override - public void scrollToPosition(int position) { - mPositionBeforeScroll = getCurrentPosition(); - mSmoothScrollTargetPosition = position; - super.scrollToPosition(position); - - getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @SuppressWarnings("deprecation") - @Override - public void onGlobalLayout() { - if (Build.VERSION.SDK_INT < 16) { - getViewTreeObserver().removeGlobalOnLayoutListener(this); - } else { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - } - - if (mSmoothScrollTargetPosition >= 0 && mSmoothScrollTargetPosition < getItemCount()) { - if (mOnPageChangedListeners != null) { - for (OnPageChangedListener onPageChangedListener : mOnPageChangedListeners) { - if (onPageChangedListener != null) { - onPageChangedListener.OnPageChanged(mPositionBeforeScroll, getCurrentPosition()); - } - } - } - } - } - }); - } - - public int getItemCount() { - return mViewPagerAdapter == null ? 0 : mViewPagerAdapter.getItemCount(); - } - - /** - * get item position in center of viewpager - */ - public int getCurrentPosition() { - LayoutManager lm = getLayoutManager(); - if (lm == null) { - return RecyclerView.NO_POSITION; - } - int curPosition; - if (lm.canScrollHorizontally()) { - curPosition = ViewUtils.getCenterXChildPosition(this); - } else { - curPosition = ViewUtils.getCenterYChildPosition(this); - } - if (curPosition < 0) { - curPosition = mSmoothScrollTargetPosition; - } - return curPosition; - } - - /*** - * adjust position before Touch event complete and fling action start. - */ - protected void adjustPositionX(int velocityX) { - if (reverseLayout) velocityX *= -1; - - int childCount = getChildCount(); - if (childCount > 0) { - int curPosition = ViewUtils.getCenterXChildPosition(this); - int childWidth = getWidth() - getPaddingLeft() - getPaddingRight(); - int flingCount = getFlingCount(velocityX, childWidth); - int targetPosition = curPosition + flingCount; - if (mSinglePageFling) { - flingCount = Math.max(-1, Math.min(1, flingCount)); - targetPosition = flingCount == 0 ? curPosition : mPositionOnTouchDown + flingCount; - } - targetPosition = Math.max(targetPosition, 0); - targetPosition = Math.min(targetPosition, getItemCount() - 1); - if (targetPosition == curPosition - && (!mSinglePageFling || mPositionOnTouchDown == curPosition)) { - View centerXChild = ViewUtils.getCenterXChild(this); - if (centerXChild != null) { - if (mTouchSpan > centerXChild.getWidth() * mTriggerOffset * mTriggerOffset && targetPosition != 0) { - if (!reverseLayout) targetPosition--; - else targetPosition++; - } else if (mTouchSpan < centerXChild.getWidth() * -mTriggerOffset && targetPosition != getItemCount() - 1) { - if (!reverseLayout) targetPosition++; - else targetPosition--; - } - } - } - smoothScrollToPosition(safeTargetPosition(targetPosition, getItemCount())); - } - } - - public void addOnPageChangedListener(OnPageChangedListener listener) { - if (mOnPageChangedListeners == null) { - mOnPageChangedListeners = new ArrayList<>(); - } - mOnPageChangedListeners.add(listener); - } - - public void removeOnPageChangedListener(OnPageChangedListener listener) { - if (mOnPageChangedListeners != null) { - mOnPageChangedListeners.remove(listener); - } - } - - public void clearOnPageChangedListeners() { - if (mOnPageChangedListeners != null) { - mOnPageChangedListeners.clear(); - } - } - - /*** - * adjust position before Touch event complete and fling action start. - */ - protected void adjustPositionY(int velocityY) { - if (reverseLayout) velocityY *= -1; - - int childCount = getChildCount(); - if (childCount > 0) { - int curPosition = ViewUtils.getCenterYChildPosition(this); - int childHeight = getHeight() - getPaddingTop() - getPaddingBottom(); - int flingCount = getFlingCount(velocityY, childHeight); - int targetPosition = curPosition + flingCount; - if (mSinglePageFling) { - flingCount = Math.max(-1, Math.min(1, flingCount)); - targetPosition = flingCount == 0 ? curPosition : mPositionOnTouchDown + flingCount; - } - - targetPosition = Math.max(targetPosition, 0); - targetPosition = Math.min(targetPosition, getItemCount() - 1); - if (targetPosition == curPosition - && (!mSinglePageFling || mPositionOnTouchDown == curPosition)) { - View centerYChild = ViewUtils.getCenterYChild(this); - if (centerYChild != null) { - if (mTouchSpan > centerYChild.getHeight() * mTriggerOffset && targetPosition != 0) { - if (!reverseLayout) targetPosition--; - else targetPosition++; - } else if (mTouchSpan < centerYChild.getHeight() * -mTriggerOffset && targetPosition != getItemCount() - 1) { - if (!reverseLayout) targetPosition++; - else targetPosition--; - } - } - } - smoothScrollToPosition(safeTargetPosition(targetPosition, getItemCount())); - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN && getLayoutManager() != null) { - mPositionOnTouchDown = getLayoutManager().canScrollHorizontally() - ? ViewUtils.getCenterXChildPosition(this) - : ViewUtils.getCenterYChildPosition(this); - } - return super.dispatchTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent e) { - // recording the max/min value in touch track - if (e.getAction() == MotionEvent.ACTION_MOVE) { - if (mCurView != null) { - mMaxLeftWhenDragging = Math.max(mCurView.getLeft(), mMaxLeftWhenDragging); - mMaxTopWhenDragging = Math.max(mCurView.getTop(), mMaxTopWhenDragging); - mMinLeftWhenDragging = Math.min(mCurView.getLeft(), mMinLeftWhenDragging); - mMinTopWhenDragging = Math.min(mCurView.getTop(), mMinTopWhenDragging); - } - } - return super.onTouchEvent(e); - } - - @Override - public void onScrollStateChanged(int state) { - super.onScrollStateChanged(state); - if (state == SCROLL_STATE_DRAGGING) { - mNeedAdjust = true; - mCurView = getLayoutManager().canScrollHorizontally() ? ViewUtils.getCenterXChild(this) : - ViewUtils.getCenterYChild(this); - if (mCurView != null) { - if (mHasCalledOnPageChanged) { - // While rvp is scrolling, mPositionBeforeScroll will be previous value. - mPositionBeforeScroll = getChildLayoutPosition(mCurView); - mHasCalledOnPageChanged = false; - } - mFisrtLeftWhenDragging = mCurView.getLeft(); - mFirstTopWhenDragging = mCurView.getTop(); - } else { - mPositionBeforeScroll = -1; - } - mTouchSpan = 0; - } else if (state == SCROLL_STATE_SETTLING) { - mNeedAdjust = false; - if (mCurView != null) { - if (getLayoutManager().canScrollHorizontally()) { - mTouchSpan = mCurView.getLeft() - mFisrtLeftWhenDragging; - } else { - mTouchSpan = mCurView.getTop() - mFirstTopWhenDragging; - } - } else { - mTouchSpan = 0; - } - mCurView = null; - } else if (state == SCROLL_STATE_IDLE) { - if (mNeedAdjust && mSticky) { - int targetPosition = getLayoutManager().canScrollHorizontally() ? ViewUtils.getCenterXChildPosition(this) : - ViewUtils.getCenterYChildPosition(this); - if (mCurView != null) { - targetPosition = getChildAdapterPosition(mCurView); - if (getLayoutManager().canScrollHorizontally()) { - int spanX = mCurView.getLeft() - mFisrtLeftWhenDragging; - // if user is tending to cancel paging action, don't perform position changing - if (spanX > mCurView.getWidth() * mTriggerOffset && mCurView.getLeft() >= mMaxLeftWhenDragging) { - if (!reverseLayout) targetPosition--; - else targetPosition++; - } else if (spanX < mCurView.getWidth() * -mTriggerOffset && mCurView.getLeft() <= mMinLeftWhenDragging) { - if (!reverseLayout) targetPosition++; - else targetPosition--; - } - } else { - int spanY = mCurView.getTop() - mFirstTopWhenDragging; - if (spanY > mCurView.getHeight() * mTriggerOffset && mCurView.getTop() >= mMaxTopWhenDragging) { - if (!reverseLayout) targetPosition--; - else targetPosition++; - } else if (spanY < mCurView.getHeight() * -mTriggerOffset && mCurView.getTop() <= mMinTopWhenDragging) { - if (!reverseLayout) targetPosition++; - else targetPosition--; - } - } - } - smoothScrollToPosition(safeTargetPosition(targetPosition, getItemCount())); - mCurView = null; - } else if (mSmoothScrollTargetPosition != mPositionBeforeScroll) { - if (mOnPageChangedListeners != null) { - for (OnPageChangedListener onPageChangedListener : mOnPageChangedListeners) { - if (onPageChangedListener != null) { - onPageChangedListener.OnPageChanged(mPositionBeforeScroll, mSmoothScrollTargetPosition); - } - } - } - mHasCalledOnPageChanged = true; - mPositionBeforeScroll = mSmoothScrollTargetPosition; - } - // resetZoom - mMaxLeftWhenDragging = Integer.MIN_VALUE; - mMinLeftWhenDragging = Integer.MAX_VALUE; - mMaxTopWhenDragging = Integer.MIN_VALUE; - mMinTopWhenDragging = Integer.MAX_VALUE; - } - } - - private int getFlingCount(int velocity, int cellSize) { - if (velocity == 0) { - return 0; - } - int sign = velocity > 0 ? 1 : -1; - return (int) (sign * Math.ceil((velocity * sign * mFlingFactor / cellSize) - - mTriggerOffset)); - } - - private int safeTargetPosition(int position, int count) { - if (position < 0) { - return 0; - } - if (position >= count) { - return count - 1; - } - return position; - } - - public interface OnPageChangedListener { - void OnPageChanged(int oldPosition, int newPosition); - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/ViewUtils.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/ViewUtils.java deleted file mode 100644 index b76a994f..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/ViewUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager; - -import android.support.v7.widget.RecyclerView; -import android.view.View; - -/** - * Source from lsjwzh/RecyclerViewPager - * https://github.com/lsjwzh/RecyclerViewPager - */ - -public class ViewUtils { - - /** - * Get center child in X Axes - */ - public static View getCenterXChild(RecyclerView recyclerView) { - int childCount = recyclerView.getChildCount(); - if (childCount > 0) { - for (int i = 0; i < childCount; i++) { - View child = recyclerView.getChildAt(i); - if (isChildInCenterX(recyclerView, child)) { - return child; - } - } - } - return null; - } - - /** - * Get position of center child in X Axes - */ - public static int getCenterXChildPosition(RecyclerView recyclerView) { - int childCount = recyclerView.getChildCount(); - if (childCount > 0) { - for (int i = 0; i < childCount; i++) { - View child = recyclerView.getChildAt(i); - if (isChildInCenterX(recyclerView, child)) { - return recyclerView.getChildAdapterPosition(child); - } - } - } - return childCount; - } - - /** - * Get center child in Y Axes - */ - public static View getCenterYChild(RecyclerView recyclerView) { - int childCount = recyclerView.getChildCount(); - if (childCount > 0) { - for (int i = 0; i < childCount; i++) { - View child = recyclerView.getChildAt(i); - if (isChildInCenterY(recyclerView, child)) { - return child; - } - } - } - return null; - } - - /** - * Get position of center child in Y Axes - */ - public static int getCenterYChildPosition(RecyclerView recyclerView) { - int childCount = recyclerView.getChildCount(); - if (childCount > 0) { - for (int i = 0; i < childCount; i++) { - View child = recyclerView.getChildAt(i); - if (isChildInCenterY(recyclerView, child)) { - return recyclerView.getChildAdapterPosition(child); - } - } - } - return childCount; - } - - public static boolean isChildInCenterX(RecyclerView recyclerView, View view) { - int childCount = recyclerView.getChildCount(); - int[] lvLocationOnScreen = new int[2]; - int[] vLocationOnScreen = new int[2]; - recyclerView.getLocationOnScreen(lvLocationOnScreen); - int middleX = lvLocationOnScreen[0] + recyclerView.getWidth() / 2; - if (childCount > 0) { - view.getLocationOnScreen(vLocationOnScreen); - if (vLocationOnScreen[0] <= middleX && vLocationOnScreen[0] + view.getWidth() >= middleX) { - return true; - } - } - return false; - } - - public static boolean isChildInCenterY(RecyclerView recyclerView, View view) { - int childCount = recyclerView.getChildCount(); - int[] lvLocationOnScreen = new int[2]; - int[] vLocationOnScreen = new int[2]; - recyclerView.getLocationOnScreen(lvLocationOnScreen); - int middleY = lvLocationOnScreen[1] + recyclerView.getHeight() / 2; - if (childCount > 0) { - view.getLocationOnScreen(vLocationOnScreen); - if (vLocationOnScreen[1] <= middleY && vLocationOnScreen[1] + view.getHeight() >= middleY) { - return true; - } - } - return false; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/HorizontalOverScrollBounceEffectDecorator.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/HorizontalOverScrollBounceEffectDecorator.java deleted file mode 100644 index 8c88b819..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/HorizontalOverScrollBounceEffectDecorator.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -import android.view.MotionEvent; -import android.view.View; - -/** - * A concrete implementation of {@link OverScrollBounceEffectDecoratorBase} for a horizontal orientation. - * - * @author amit - */ -public class HorizontalOverScrollBounceEffectDecorator extends OverScrollBounceEffectDecoratorBase { - - protected static class MotionAttributesHorizontal extends MotionAttributes { - - public boolean init(View view, MotionEvent event) { - - // We must have history available to calc the dx. Normally it's there - if it isn't temporarily, - // we declare the event 'invalid' and expect it in consequent events. - if (event.getHistorySize() == 0) { - return false; - } - - // Allow for counter-orientation-direction operations (e.g. item swiping) to run fluently. - final float dy = event.getY(0) - event.getHistoricalY(0, 0); - final float dx = event.getX(0) - event.getHistoricalX(0, 0); - if (Math.abs(dx) < Math.abs(dy)) { - return false; - } - - mAbsOffset = view.getTranslationX(); - mDeltaOffset = dx; - mDir = mDeltaOffset > 0; - - return true; - } - } - - protected static class AnimationAttributesHorizontal extends AnimationAttributes { - - public AnimationAttributesHorizontal() { - mProperty = View.TRANSLATION_X; - } - - @Override - protected void init(View view) { - mAbsOffset = view.getTranslationX(); - mMaxOffset = view.getWidth(); - } - } - - /** - * C'tor, creating the effect with default arguments: - *
Touch-drag ratio in 'forward' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD. - *
Touch-drag ratio in 'backwards' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK. - *
Deceleration factor (for the bounce-back effect) will be set to DEFAULT_DECELERATE_FACTOR. - * - * @param viewAdapter The view's encapsulation. - */ - public HorizontalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter) { - this(viewAdapter, DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD, DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK, DEFAULT_DECELERATE_FACTOR); - } - - /** - * C'tor, creating the effect with explicit arguments. - * - * @param viewAdapter The view's encapsulation. - * @param touchDragRatioFwd Ratio of touch distance to actual drag distance when in 'forward' direction. - * @param touchDragRatioBck Ratio of touch distance to actual drag distance when in 'backward' - * direction (opposite to initial one). - * @param decelerateFactor Deceleration factor used when decelerating the motion to create the - * bounce-back effect. - */ - public HorizontalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter, - float touchDragRatioFwd, float touchDragRatioBck, float decelerateFactor) { - super(viewAdapter, decelerateFactor, touchDragRatioFwd, touchDragRatioBck); - } - - @Override - protected MotionAttributes createMotionAttributes() { - return new MotionAttributesHorizontal(); - } - - @Override - protected AnimationAttributes createAnimationAttributes() { - return new AnimationAttributesHorizontal(); - } - - @Override - protected void translateView(View view, float offset) { - view.setTranslationX(offset); - } - - @Override - protected void translateViewAndEvent(View view, float offset, MotionEvent event) { - view.setTranslationX(offset); - event.offsetLocation(offset - event.getX(0), 0f); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollDecor.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollDecor.java deleted file mode 100644 index fa0a58e0..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollDecor.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -import android.view.View; - -/** - * @author amit - */ -public interface IOverScrollDecor { - View getView(); - - void setOverScrollStateListener(IOverScrollStateListener listener); - void setOverScrollUpdateListener(IOverScrollUpdateListener listener); - - /** - * Get the current decorator's runtime state, i.e. one of the values specified by {@link IOverScrollState}. - * @return The state. - */ - int getCurrentState(); - - /** - * Detach the decorator from its associated view, thus disabling it entirely. - * - *

It is best to call this only when over-scroll isn't currently in-effect - i.e. verify that - * getCurrentState()==IOverScrollState.STATE_IDLE as a precondition, or otherwise - * use a state listener previously installed using - * {@link #setOverScrollStateListener(IOverScrollStateListener)}.

- * - *

Note: Upon detachment completion, the view in question will return to the default - * Android over-scroll configuration (i.e. {@link View.OVER_SCROLL_ALWAYS} mode). This can be - * overridden by calling View.setOverScrollMode(mode) immediately thereafter.

- */ - void detach(); -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollDecoratorAdapter.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollDecoratorAdapter.java deleted file mode 100644 index fbfe92d6..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollDecoratorAdapter.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -import android.view.View; - -/** - * @author amitd - * - * @see HorizontalOverScrollBounceEffectDecorator - */ -public interface IOverScrollDecoratorAdapter { - - View getView(); - - /** - * Is view in it's absolute start position - such that a negative over-scroll can potentially - * be initiated. For example, in list-views, this is synonymous with the first item being - * fully visible. - * - * @return Whether in absolute start position. - */ - boolean isInAbsoluteStart(); - - /** - * Is view in it's absolute end position - such that an over-scroll can potentially - * be initiated. For example, in list-views, this is synonymous with the last item being - * fully visible. - * - * @return Whether in absolute end position. - */ - boolean isInAbsoluteEnd(); -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollState.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollState.java deleted file mode 100644 index f9799f15..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollState.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -/** - * @author amit - */ -public interface IOverScrollState { - - /** No over-scroll is in-effect. */ - int STATE_IDLE = 0; - - /** User is actively touch-dragging, thus enabling over-scroll at the view's start side. */ - int STATE_DRAG_START_SIDE = 1; - - /** User is actively touch-dragging, thus enabling over-scroll at the view's end side. */ - int STATE_DRAG_END_SIDE = 2; - - /** User has released their touch, thus throwing the view back into place via bounce-back animation. */ - int STATE_BOUNCE_BACK = 3; -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollStateListener.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollStateListener.java deleted file mode 100644 index 89a3bf30..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollStateListener.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -/** - * A callback-listener enabling over-scroll effect clients to be notified of effect state transitions. - *
Invoked whenever state is transitioned onto one of {@link IOverScrollState#STATE_IDLE}, - * {@link IOverScrollState#STATE_DRAG_START_SIDE}, {@link IOverScrollState#STATE_DRAG_END_SIDE} - * or {@link IOverScrollState#STATE_BOUNCE_BACK}. - * - * @author amit - * - * @see IOverScrollUpdateListener - */ -public interface IOverScrollStateListener { - - /** - * The invoked callback. - * - * @param decor The associated over-scroll 'decorator'. - * @param oldState The old over-scroll state; ID's specified by {@link IOverScrollState}, e.g. - * {@link IOverScrollState#STATE_IDLE}. - * @param newState The new over-scroll state; ID's specified by {@link IOverScrollState}, - * e.g. {@link IOverScrollState#STATE_IDLE}. - */ - void onOverScrollStateChange(IOverScrollDecor decor, int oldState, int newState); -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollUpdateListener.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollUpdateListener.java deleted file mode 100644 index 4a2ee1c0..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/IOverScrollUpdateListener.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -/** - * A callback-listener enabling over-scroll effect clients to subscribe to real-time updates - * of over-scrolling intensity, provided as the view-translation offset from pre-scroll position. - * - * @author amit - * - * @see IOverScrollStateListener - */ -public interface IOverScrollUpdateListener { - - /** - * The invoked callback. - * - * @param decor The associated over-scroll 'decorator'. - * @param state One of: {@link IOverScrollState#STATE_IDLE}, {@link IOverScrollState#STATE_DRAG_START_SIDE}, - * {@link IOverScrollState#STATE_DRAG_START_SIDE} or {@link IOverScrollState#STATE_BOUNCE_BACK}. - * @param offset The currently visible offset created due to over-scroll. - */ - void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset); -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/ListenerStubs.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/ListenerStubs.java deleted file mode 100644 index 6b1ed8d1..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/ListenerStubs.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - - -/** - * @author amit - */ -public interface ListenerStubs { - - class OverScrollStateListenerStub implements IOverScrollStateListener { - @Override - public void onOverScrollStateChange(IOverScrollDecor decor, int oldState, int newState) { } - } - - class OverScrollUpdateListenerStub implements IOverScrollUpdateListener { - @Override - public void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset) { } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/OverScrollBounceEffectDecoratorBase.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/OverScrollBounceEffectDecoratorBase.java deleted file mode 100644 index 07afb353..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/OverScrollBounceEffectDecoratorBase.java +++ /dev/null @@ -1,493 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.util.Log; -import android.util.Property; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; - -import static org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollState.STATE_BOUNCE_BACK; -import static org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollState.STATE_DRAG_END_SIDE; -import static org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollState.STATE_DRAG_START_SIDE; -import static org.nv95.openmanga.components.reader.recyclerpager.overscroll.IOverScrollState.STATE_IDLE; - -/** - * A standalone view decorator adding over-scroll with a smooth bounce-back effect to (potentially) any view - - * provided that an appropriate {@link IOverScrollDecoratorAdapter} implementation exists / can be written - * for that view type (e.g. {@link RecyclerViewOverScrollDecorAdapter}). - *

- *

Design-wise, being a standalone class, this decorator powerfully provides the ability to add - * the over-scroll effect over any view without adjusting the view's implementation. In essence, this - * eliminates the need to repeatedly implement the effect per each view type (list-view, - * recycler-view, image-view, etc.). Therefore, using it is highly recommended compared to other - * more intrusive solutions.

- *

- *

Note that this class is abstract, having {@link HorizontalOverScrollBounceEffectDecorator} and - * {@link VerticalOverScrollBounceEffectDecorator} providing concrete implementations that are - * view-orientation specific.

- *

- *


- *

Implementation Notes

- *

- *

At it's core, the class simply registers itself as a touch-listener over the decorated view and - * intercepts touch events as needed.

- *

- *

Internally, it delegates the over-scrolling calculations onto 3 state-based classes: - *

    - *
  1. Idle state - monitors view state and touch events to intercept over-scrolling initiation - * (in which case it hands control over to the Over-scrolling state).
  2. - *
  3. Over-scrolling state - handles motion events to apply the over-scroll effect as users - * interact with the view.
  4. - *
  5. Bounce-back state - runs the bounce-back animation, all-the-while blocking all - * touch events till the animation completes (in which case it hands control back to the idle - * state).
  6. - *
- *

- * - * @author amit - * @see RecyclerViewOverScrollDecorAdapter - * @see IOverScrollDecoratorAdapter - */ -public abstract class OverScrollBounceEffectDecoratorBase implements IOverScrollDecor, View.OnTouchListener { - - public static final String TAG = "OverScrollDecor"; - - public static final float DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD = 3f; - public static final float DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK = 1f; - public static final float DEFAULT_DECELERATE_FACTOR = -2f; - - protected static final int MAX_BOUNCE_BACK_DURATION_MS = 800; - protected static final int MIN_BOUNCE_BACK_DURATION_MS = 200; - - protected final OverScrollStartAttributes mStartAttr = new OverScrollStartAttributes(); - protected final IOverScrollDecoratorAdapter mViewAdapter; - - protected final IdleState mIdleState; - protected final OverScrollingState mOverScrollingState; - protected final BounceBackState mBounceBackState; - protected IDecoratorState mCurrentState; - - protected IOverScrollStateListener mStateListener = new ListenerStubs.OverScrollStateListenerStub(); - protected IOverScrollUpdateListener mUpdateListener = new ListenerStubs.OverScrollUpdateListenerStub(); - - /** - * When in over-scroll mode, keep track of dragging velocity to provide a smooth slow-down - * for the bounce-back effect. - */ - protected float mVelocity; - - /** - * Motion attributes: keeps data describing current motion event. - *
Orientation agnostic: subclasses provide either horizontal or vertical - * initialization of the agnostic attributes. - */ - protected abstract static class MotionAttributes { - public float mAbsOffset; - public float mDeltaOffset; - public boolean mDir; // True = 'forward', false = 'backwards'. - - protected abstract boolean init(View view, MotionEvent event); - } - - protected static class OverScrollStartAttributes { - protected int mPointerId; - protected float mAbsOffset; - protected boolean mDir; // True = 'forward', false = 'backwards'. - } - - protected abstract static class AnimationAttributes { - public Property mProperty; - public float mAbsOffset; - public float mMaxOffset; - - protected abstract void init(View view); - } - - /** - * Interface of decorator-state delegation classes. Defines states as handles of two fundamental - * touch events: actual movement, up/cancel. - */ - protected interface IDecoratorState { - - /** - * Handle a motion (touch) event. - * - * @param event The event from onTouch. - * @return Return value for onTouch. - */ - boolean handleMoveTouchEvent(MotionEvent event); - - /** - * Handle up / touch-cancel events. - * - * @param event The event from onTouch. - * @return Return value for onTouch. - */ - boolean handleUpOrCancelTouchEvent(MotionEvent event); - - /** - * Handle a transition onto this state, as it becomes 'current' state. - * - * @param fromState - */ - void handleEntryTransition(IDecoratorState fromState); - - /** - * The client-perspective ID of the state associated with this (internal) one. ID's - * are as specified in {@link IOverScrollState}. - * - * @return The ID, e.g. {@link IOverScrollState#STATE_IDLE}. - */ - int getStateId(); - } - - /** - * Idle state: monitors move events, trying to figure out whether over-scrolling should be - * initiated (i.e. when scrolled further when the view is at one of its displayable ends). - *
When such is the case, it hands over control to the over-scrolling state. - */ - protected class IdleState implements IDecoratorState { - - final MotionAttributes mMoveAttr; - - public IdleState() { - mMoveAttr = createMotionAttributes(); - } - - @Override - public int getStateId() { - return STATE_IDLE; - } - - @Override - public boolean handleMoveTouchEvent(MotionEvent event) { - - final View view = mViewAdapter.getView(); - if (!mMoveAttr.init(view, event)) { - return false; - } - - // Has over-scrolling officially started? - if ((mViewAdapter.isInAbsoluteStart() && mMoveAttr.mDir) || - (mViewAdapter.isInAbsoluteEnd() && !mMoveAttr.mDir)) { - - // Save initial over-scroll attributes for future reference. - mStartAttr.mPointerId = event.getPointerId(0); - mStartAttr.mAbsOffset = mMoveAttr.mAbsOffset; - mStartAttr.mDir = mMoveAttr.mDir; - - issueStateTransition(mOverScrollingState); - return mOverScrollingState.handleMoveTouchEvent(event); - } - - return false; - } - - @Override - public boolean handleUpOrCancelTouchEvent(MotionEvent event) { - return false; - } - - @Override - public void handleEntryTransition(IDecoratorState fromState) { - mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId()); - } - } - - /** - * Handles the actual over-scrolling: thus translating the view according to configuration - * and user interactions, dynamically. - *

- *

The state is exited - thus completing over-scroll handling, in one of two cases: - *
When user lets go of the view, it transitions control to the bounce-back state. - *
When user moves the view back onto a potential 'under-scroll' state, it abruptly - * transitions control to the idle-state, so as to return touch-events management to the - * normal over-scroll-less environment (thus preventing under-scrolling and potentially regaining - * regular scrolling). - */ - protected class OverScrollingState implements IDecoratorState { - - protected final float mTouchDragRatioFwd; - protected final float mTouchDragRatioBck; - - final MotionAttributes mMoveAttr; - int mCurrDragState; - - public OverScrollingState(float touchDragRatioFwd, float touchDragRatioBck) { - mMoveAttr = createMotionAttributes(); - mTouchDragRatioFwd = touchDragRatioFwd; - mTouchDragRatioBck = touchDragRatioBck; - } - - @Override - public int getStateId() { - // This is really a single class that implements 2 states, so our ID depends on what - // it was during the last invocation. - return mCurrDragState; - } - - @Override - public boolean handleMoveTouchEvent(MotionEvent event) { - - // Switching 'pointers' (e.g. fingers) on-the-fly isn't supported -- abort over-scroll - // smoothly using the default bounce-back animation in this case. - if (mStartAttr.mPointerId != event.getPointerId(0)) { - issueStateTransition(mBounceBackState); - return true; - } - - final View view = mViewAdapter.getView(); - if (!mMoveAttr.init(view, event)) { - // Keep intercepting the touch event as long as we're still over-scrolling... - return true; - } - - float deltaOffset = mMoveAttr.mDeltaOffset / (mMoveAttr.mDir == mStartAttr.mDir ? mTouchDragRatioFwd : mTouchDragRatioBck); - float newOffset = mMoveAttr.mAbsOffset + deltaOffset; - - // If moved in counter direction onto a potential under-scroll state -- don't. Instead, abort - // over-scrolling abruptly, thus returning control to which-ever touch handlers there - // are waiting (e.g. regular scroller handlers). - if ((mStartAttr.mDir && !mMoveAttr.mDir && (newOffset <= mStartAttr.mAbsOffset)) || - (!mStartAttr.mDir && mMoveAttr.mDir && (newOffset >= mStartAttr.mAbsOffset))) { - translateViewAndEvent(view, mStartAttr.mAbsOffset, event); - mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, mCurrDragState, 0); - - issueStateTransition(mIdleState); - return true; - } - - if (view.getParent() != null) { - view.getParent().requestDisallowInterceptTouchEvent(true); - } - - long dt = event.getEventTime() - event.getHistoricalEventTime(0); - if (dt > 0) { // Sometimes (though rarely) dt==0 cause originally timing is in nanos, but is presented in millis. - mVelocity = deltaOffset / dt; - } - - translateView(view, newOffset); - mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, mCurrDragState, newOffset); - - return true; - } - - @Override - public boolean handleUpOrCancelTouchEvent(MotionEvent event) { - issueStateTransition(mBounceBackState); - return false; - } - - @Override - public void handleEntryTransition(IDecoratorState fromState) { - mCurrDragState = (mStartAttr.mDir ? STATE_DRAG_START_SIDE : STATE_DRAG_END_SIDE); - mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId()); - } - } - - /** - * When entered, starts the bounce-back animation. - *
Upon animation completion, transitions control onto the idle state; Does so by - * registering itself as an animation listener. - *
In the meantime, blocks (intercepts) all touch events. - */ - protected class BounceBackState implements IDecoratorState, Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { - - protected final Interpolator mBounceBackInterpolator = new DecelerateInterpolator(); - protected final float mDecelerateFactor; - protected final float mDoubleDecelerateFactor; - - protected final AnimationAttributes mAnimAttributes; - - public BounceBackState(float decelerateFactor) { - mDecelerateFactor = decelerateFactor; - mDoubleDecelerateFactor = 2f * decelerateFactor; - - mAnimAttributes = createAnimationAttributes(); - } - - @Override - public int getStateId() { - return STATE_BOUNCE_BACK; - } - - @Override - public void handleEntryTransition(IDecoratorState fromState) { - - mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId()); - - Animator bounceBackAnim = createAnimator(); - bounceBackAnim.addListener(this); - - bounceBackAnim.start(); - } - - @Override - public boolean handleMoveTouchEvent(MotionEvent event) { - // Flush all touches down the drain till animation is over. - return true; - } - - @Override - public boolean handleUpOrCancelTouchEvent(MotionEvent event) { - // Flush all touches down the drain till animation is over. - return true; - } - - @Override - public void onAnimationEnd(Animator animation) { - issueStateTransition(mIdleState); - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, STATE_BOUNCE_BACK, (Float) animation.getAnimatedValue()); - } - - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - protected Animator createAnimator() { - - final View view = mViewAdapter.getView(); - - mAnimAttributes.init(view); - - // Set up a low-duration slow-down animation IN the drag direction. - - // Exception: If wasn't dragging in 'forward' direction (or velocity=0 -- i.e. not dragging at all), - // skip slow-down anim directly to the bounce-back. - if (mVelocity == 0f || (mVelocity < 0 && mStartAttr.mDir) || (mVelocity > 0 && !mStartAttr.mDir)) { - return createBounceBackAnimator(mAnimAttributes.mAbsOffset); - } - - // dt = (Vt - Vo) / a; Vt=0 ==> dt = -Vo / a - float slowdownDuration = -mVelocity / mDecelerateFactor; - slowdownDuration = (slowdownDuration < 0 ? 0 : slowdownDuration); // Happens in counter-direction dragging - - // dx = (Vt^2 - Vo^2) / 2a; Vt=0 ==> dx = -Vo^2 / 2a - float slowdownDistance = -mVelocity * mVelocity / mDoubleDecelerateFactor; - float slowdownEndOffset = mAnimAttributes.mAbsOffset + slowdownDistance; - - ObjectAnimator slowdownAnim = createSlowdownAnimator(view, (int) slowdownDuration, slowdownEndOffset); - - // Set up the bounce back animation, bringing the view back into the original, pre-overscroll position (translation=0). - - ObjectAnimator bounceBackAnim = createBounceBackAnimator(slowdownEndOffset); - - // Play the 2 animations as a sequence. - AnimatorSet wholeAnim = new AnimatorSet(); - wholeAnim.playSequentially(slowdownAnim, bounceBackAnim); - return wholeAnim; - } - - protected ObjectAnimator createSlowdownAnimator(View view, int slowdownDuration, float slowdownEndOffset) { - ObjectAnimator slowdownAnim = ObjectAnimator.ofFloat(view, mAnimAttributes.mProperty, slowdownEndOffset); - slowdownAnim.setDuration(slowdownDuration); - slowdownAnim.setInterpolator(mBounceBackInterpolator); - slowdownAnim.addUpdateListener(this); - return slowdownAnim; - } - - protected ObjectAnimator createBounceBackAnimator(float startOffset) { - - final View view = mViewAdapter.getView(); - - // Duration is proportional to the view's size. - float bounceBackDuration = (Math.abs(startOffset) / mAnimAttributes.mMaxOffset) * MAX_BOUNCE_BACK_DURATION_MS; - ObjectAnimator bounceBackAnim = ObjectAnimator.ofFloat(view, mAnimAttributes.mProperty, mStartAttr.mAbsOffset); - bounceBackAnim.setDuration(Math.max((int) bounceBackDuration, MIN_BOUNCE_BACK_DURATION_MS)); - bounceBackAnim.setInterpolator(mBounceBackInterpolator); - bounceBackAnim.addUpdateListener(this); - return bounceBackAnim; - } - } - - public OverScrollBounceEffectDecoratorBase(IOverScrollDecoratorAdapter viewAdapter, float decelerateFactor, float touchDragRatioFwd, float touchDragRatioBck) { - mViewAdapter = viewAdapter; - - mBounceBackState = new BounceBackState(decelerateFactor); - mOverScrollingState = new OverScrollingState(touchDragRatioFwd, touchDragRatioBck); - mIdleState = new IdleState(); - - mCurrentState = mIdleState; - - attach(); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_MOVE: - return mCurrentState.handleMoveTouchEvent(event); - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - return mCurrentState.handleUpOrCancelTouchEvent(event); - } - - return false; - } - - @Override - public void setOverScrollStateListener(IOverScrollStateListener listener) { - mStateListener = (listener != null ? listener : new ListenerStubs.OverScrollStateListenerStub()); - } - - @Override - public void setOverScrollUpdateListener(IOverScrollUpdateListener listener) { - mUpdateListener = (listener != null ? listener : new ListenerStubs.OverScrollUpdateListenerStub()); - } - - @Override - public int getCurrentState() { - return mCurrentState.getStateId(); - } - - @Override - public View getView() { - return mViewAdapter.getView(); - } - - protected void issueStateTransition(IDecoratorState state) { - IDecoratorState oldState = mCurrentState; - mCurrentState = state; - mCurrentState.handleEntryTransition(oldState); - } - - protected void attach() { - getView().setOnTouchListener(this); - getView().setOverScrollMode(View.OVER_SCROLL_NEVER); - } - - @Override - public void detach() { - if (mCurrentState != mIdleState) { - Log.w(TAG, "Decorator detached while over-scroll is in effect. You might want to add a precondition of that getCurrentState()==STATE_IDLE, first."); - } - getView().setOnTouchListener(null); - getView().setOverScrollMode(View.OVER_SCROLL_ALWAYS); - } - - protected abstract MotionAttributes createMotionAttributes(); - - protected abstract AnimationAttributes createAnimationAttributes(); - - protected abstract void translateView(View view, float offset); - - protected abstract void translateViewAndEvent(View view, float offset, MotionEvent event); -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/RecyclerViewOverScrollDecorAdapter.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/RecyclerViewOverScrollDecorAdapter.java deleted file mode 100644 index 89e78e66..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/RecyclerViewOverScrollDecorAdapter.java +++ /dev/null @@ -1,223 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -import android.graphics.Canvas; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.StaggeredGridLayoutManager; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.view.View; - -import java.util.List; - - -/** - * @author amitd - * @see HorizontalOverScrollBounceEffectDecorator - * @see VerticalOverScrollBounceEffectDecorator - */ -public class RecyclerViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter { - - /** - * A delegation of the adapter implementation of this view that should provide the processing - * of {@link #isInAbsoluteStart()} and {@link #isInAbsoluteEnd()}. Essentially needed simply - * because the implementation depends on the layout manager implementation being used. - */ - protected interface Impl { - boolean isInAbsoluteStart(); - - boolean isInAbsoluteEnd(); - } - - protected final RecyclerView mRecyclerView; - protected final Impl mImpl; - - protected boolean mIsItemTouchInEffect = false; - - public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView) { - - mRecyclerView = recyclerView; - - final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); - if (layoutManager instanceof LinearLayoutManager || - layoutManager instanceof StaggeredGridLayoutManager) { - final int orientation = - (layoutManager instanceof LinearLayoutManager - ? ((LinearLayoutManager) layoutManager).getOrientation() - : ((StaggeredGridLayoutManager) layoutManager).getOrientation()); - - if (orientation == LinearLayoutManager.HORIZONTAL) { - mImpl = new ImplHorizLayout(); - } else { - mImpl = new ImplVerticalLayout(); - } - } else { - throw new IllegalArgumentException("Recycler views with custom layout managers are not supported by this adapter out of the box." + - "Try implementing and providing an explicit 'impl' parameter to the other c'tors, or otherwise create a custom adapter subclass of your own."); - } - } - - public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView, Impl impl) { - mRecyclerView = recyclerView; - mImpl = impl; - } - - public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView, ItemTouchHelper.Callback itemTouchHelperCallback) { - this(recyclerView); - setUpTouchHelperCallback(itemTouchHelperCallback); - } - - public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView, Impl impl, ItemTouchHelper.Callback itemTouchHelperCallback) { - this(recyclerView, impl); - setUpTouchHelperCallback(itemTouchHelperCallback); - } - - protected void setUpTouchHelperCallback(final ItemTouchHelper.Callback itemTouchHelperCallback) { - new ItemTouchHelper(new ItemTouchHelperCallbackWrapper(itemTouchHelperCallback) { - @Override - public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { - mIsItemTouchInEffect = actionState != 0; - super.onSelectedChanged(viewHolder, actionState); - } - }).attachToRecyclerView(mRecyclerView); - } - - @Override - public View getView() { - return mRecyclerView; - } - - @Override - public boolean isInAbsoluteStart() { - return !mIsItemTouchInEffect && mImpl.isInAbsoluteStart(); - } - - @Override - public boolean isInAbsoluteEnd() { - return !mIsItemTouchInEffect && mImpl.isInAbsoluteEnd(); - } - - protected class ImplHorizLayout implements Impl { - - @Override - public boolean isInAbsoluteStart() { - return !mRecyclerView.canScrollHorizontally(-1); - } - - @Override - public boolean isInAbsoluteEnd() { - return !mRecyclerView.canScrollHorizontally(1); - } - } - - protected class ImplVerticalLayout implements Impl { - - @Override - public boolean isInAbsoluteStart() { - return !mRecyclerView.canScrollVertically(-1); - } - - @Override - public boolean isInAbsoluteEnd() { - return !mRecyclerView.canScrollVertically(1); - } - } - - private static class ItemTouchHelperCallbackWrapper extends ItemTouchHelper.Callback { - - final ItemTouchHelper.Callback mCallback; - - private ItemTouchHelperCallbackWrapper(ItemTouchHelper.Callback callback) { - mCallback = callback; - } - - @Override - public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - return mCallback.getMovementFlags(recyclerView, viewHolder); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - return mCallback.onMove(recyclerView, viewHolder, target); - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - mCallback.onSwiped(viewHolder, direction); - } - - @Override - public int convertToAbsoluteDirection(int flags, int layoutDirection) { - return mCallback.convertToAbsoluteDirection(flags, layoutDirection); - } - - @Override - public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) { - return mCallback.canDropOver(recyclerView, current, target); - } - - @Override - public boolean isLongPressDragEnabled() { - return mCallback.isLongPressDragEnabled(); - } - - @Override - public boolean isItemViewSwipeEnabled() { - return mCallback.isItemViewSwipeEnabled(); - } - - @Override - public int getBoundingBoxMargin() { - return mCallback.getBoundingBoxMargin(); - } - - @Override - public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { - return mCallback.getSwipeThreshold(viewHolder); - } - - @Override - public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) { - return mCallback.getMoveThreshold(viewHolder); - } - - @Override - public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected, List dropTargets, int curX, int curY) { - return mCallback.chooseDropTarget(selected, dropTargets, curX, curY); - } - - @Override - public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { - mCallback.onSelectedChanged(viewHolder, actionState); - } - - @Override - public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int fromPos, RecyclerView.ViewHolder target, int toPos, int x, int y) { - mCallback.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y); - } - - @Override - public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - mCallback.clearView(recyclerView, viewHolder); - } - - @Override - public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { - mCallback.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - } - - @Override - public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { - mCallback.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - } - - @Override - public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) { - return mCallback.getAnimationDuration(recyclerView, animationType, animateDx, animateDy); - } - - @Override - public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) { - return mCallback.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/VerticalOverScrollBounceEffectDecorator.java b/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/VerticalOverScrollBounceEffectDecorator.java deleted file mode 100644 index 671b3331..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/recyclerpager/overscroll/VerticalOverScrollBounceEffectDecorator.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.nv95.openmanga.components.reader.recyclerpager.overscroll; - -import android.view.MotionEvent; -import android.view.View; - -/** - * A concrete implementation of {@link OverScrollBounceEffectDecoratorBase} for a vertical orientation. - * - * @author amit - */ -public class VerticalOverScrollBounceEffectDecorator extends OverScrollBounceEffectDecoratorBase { - - protected static class MotionAttributesVertical extends MotionAttributes { - - public boolean init(View view, MotionEvent event) { - - // We must have history available to calc the dx. Normally it's there - if it isn't temporarily, - // we declare the event 'invalid' and expect it in consequent events. - if (event.getHistorySize() == 0) { - return false; - } - - // Allow for counter-orientation-direction operations (e.g. item swiping) to run fluently. - final float dy = event.getY(0) - event.getHistoricalY(0, 0); - final float dx = event.getX(0) - event.getHistoricalX(0, 0); - if (Math.abs(dx) > Math.abs(dy)) { - return false; - } - - mAbsOffset = view.getTranslationY(); - mDeltaOffset = dy; - mDir = mDeltaOffset > 0; - - return true; - } - } - - protected static class AnimationAttributesVertical extends AnimationAttributes { - - public AnimationAttributesVertical() { - mProperty = View.TRANSLATION_Y; - } - - @Override - protected void init(View view) { - mAbsOffset = view.getTranslationY(); - mMaxOffset = view.getHeight(); - } - } - - /** - * C'tor, creating the effect with default arguments: - *
Touch-drag ratio in 'forward' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD. - *
Touch-drag ratio in 'backwards' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK. - *
Deceleration factor (for the bounce-back effect) will be set to DEFAULT_DECELERATE_FACTOR. - * - * @param viewAdapter The view's encapsulation. - */ - public VerticalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter) { - this(viewAdapter, DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD, DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK, DEFAULT_DECELERATE_FACTOR); - } - - /** - * C'tor, creating the effect with explicit arguments. - * - * @param viewAdapter The view's encapsulation. - * @param touchDragRatioFwd Ratio of touch distance to actual drag distance when in 'forward' direction. - * @param touchDragRatioBck Ratio of touch distance to actual drag distance when in 'backward' - * direction (opposite to initial one). - * @param decelerateFactor Deceleration factor used when decelerating the motion to create the - * bounce-back effect. - */ - public VerticalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter, - float touchDragRatioFwd, float touchDragRatioBck, float decelerateFactor) { - super(viewAdapter, decelerateFactor, touchDragRatioFwd, touchDragRatioBck); - } - - @Override - protected MotionAttributes createMotionAttributes() { - return new MotionAttributesVertical(); - } - - @Override - protected AnimationAttributes createAnimationAttributes() { - return new AnimationAttributesVertical(); - } - - @Override - protected void translateView(View view, float offset) { - view.setTranslationY(offset); - } - - @Override - protected void translateViewAndEvent(View view, float offset, MotionEvent event) { - view.setTranslationY(offset); - event.offsetLocation(offset - event.getY(0), 0f); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/AsyncBitmapDecoder.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/AsyncBitmapDecoder.java deleted file mode 100644 index 91482780..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/AsyncBitmapDecoder.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; - -import java.util.concurrent.Executor; - -/** - * Created by admin on 02.08.17. - */ - -public class AsyncBitmapDecoder implements Runnable { - - private final String mFilename; - private final DecodeCallback mCallback; - - public AsyncBitmapDecoder(String filename, DecodeCallback callback) { - mFilename = filename; - mCallback = callback; - } - - @Override - public void run() { - try { - Bitmap bitmap = BitmapFactory.decodeFile(mFilename); - mCallback.onBitmapDecoded(bitmap); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public interface DecodeCallback { - @WorkerThread - void onBitmapDecoded(@Nullable Bitmap bitmap); - } - - public static void decode(String filename, DecodeCallback callback, Executor executor) { - executor.execute(new AsyncBitmapDecoder(filename, callback)); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/AsyncScroller.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/AsyncScroller.java deleted file mode 100644 index 5d011a7c..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/AsyncScroller.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.content.Context; -import android.os.Handler; -import android.os.Message; -import android.support.annotation.Nullable; -import android.widget.Scroller; - - -/** - * Created by admin on 15.08.17. - */ - -public class AsyncScroller extends Scroller implements Handler.Callback { - - private final OnFlyListener mListener; - private final Handler mHandler; - @Nullable - private WatcherThread mThread = null; - - public AsyncScroller(Context context, OnFlyListener listener) { - super(context); - mHandler = new Handler(this); - mListener = listener; - } - - @Override - public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { - if (mThread != null) { - mThread.interrupt(); - } - mThread = new WatcherThread(startX, startY); - super.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); - mThread.start(); - } - - @Override - public void startScroll(int startX, int startY, int dx, int dy) { - if (mThread != null) { - mThread.interrupt(); - } - mThread = new WatcherThread(startX, startY); - super.startScroll(startX, startY, dx, dy); - mThread.start(); - } - - @Override - public void startScroll(int startX, int startY, int dx, int dy, int duration) { - if (mThread != null) { - mThread.interrupt(); - } - mThread = new WatcherThread(startX, startY); - super.startScroll(startX, startY, dx, dy, duration); - mThread.start(); - } - - @Override - public boolean handleMessage(Message message) { - mListener.onScrolled(message.arg1, message.arg2); - return false; - } - - private class WatcherThread extends Thread { - - private int oldX, oldY; - - public WatcherThread(int oldX, int oldY) { - this.oldX = oldX; - this.oldY = oldY; - } - - @Override - public void run() { - while (!isInterrupted() && !AsyncScroller.this.isFinished()) { - if (computeScrollOffset()) { - Message msg = new Message(); - msg.arg1 = getCurrX(); - msg.arg2 = getCurrY(); - if (msg.arg1 != oldX || msg.arg2 != oldY) { - mHandler.sendMessage(msg); - oldX = msg.arg1; - oldY = msg.arg2; - } - } - } - super.run(); - } - } - - @Override - public void abortAnimation() { - if (mThread != null) { - mThread.interrupt(); - mThread = null; - } - super.abortAnimation(); - } - - public interface OnFlyListener { - void onScrolled(int currentX, int currentY); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ChangesListener.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ChangesListener.java deleted file mode 100644 index 1b2d233f..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ChangesListener.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -/** - * Created by admin on 03.08.17. - */ - -public interface ChangesListener { - - void notifyDataSetChanged(); -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/DrawableView.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/DrawableView.java deleted file mode 100644 index c31b23af..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/DrawableView.java +++ /dev/null @@ -1,348 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.SurfaceHolder; -import android.view.SurfaceView; - -import java.util.concurrent.atomic.AtomicReference; - -/** - * Created by admin on 14.08.17. - */ - -public abstract class DrawableView extends SurfaceView implements SurfaceHolder.Callback, ScaleGestureDetector.OnScaleGestureListener, AsyncScroller.OnFlyListener, ScaleAnimator.ZoomCallback { - - @Nullable - private DrawThread mThread; - private final GestureDetector mGestureDetector; - private final ScaleGestureDetector mScaleDetector; - private final AsyncScroller mScroller; - private final Rect mViewport; - protected final AtomicReference mScrollState; - - public DrawableView(Context context) { - this(context, null, 0); - } - - public DrawableView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DrawableView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mGestureDetector = new GestureDetector(context, new GestureListener()); - mScaleDetector = new ScaleGestureDetector(context, this); - mScroller = new AsyncScroller(context, this); - mViewport = new Rect(0, 0, 0, 0); - mThread = null; - mScrollState = new AtomicReference<>(new ScrollState(1f, 0, 0)); - SurfaceHolder holder = getHolder(); - holder.addCallback(this); - } - - @Override - public void surfaceCreated(SurfaceHolder surfaceHolder) { - mThread = new DrawThread(surfaceHolder); - mThread.start(); - } - - @Override - public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { - int oldHeight = mViewport.height(); - int oldWidth = mViewport.width(); - mViewport.set(0, 0, width, height); - onViewportChanged(oldHeight, oldWidth, height, width); - forceRedraw(); - } - - @Override - public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - if (mThread != null) { - mThread.interrupt(); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mGestureDetector.onTouchEvent(event); - mScaleDetector.onTouchEvent(event); - return true; - } - - @Override - public boolean onScale(ScaleGestureDetector detector) { - ScrollState state = mScrollState.get(); - - float scaleFactor = state.scale; - scaleFactor *= detector.getScaleFactor(); - if (scaleFactor < 1f) { - if (state.scale == 1f) { - return false; - } else { - scaleFactor = 1f; - } - } else if (scaleFactor > 2f) { - if (state.scale == 2f) { - return false; - } else { - scaleFactor = 2f; - } - } - - - float scrollFactor = scaleFactor - state.scale; - int newScrollX = (int) (state.scrollX + detector.getFocusX() * scrollFactor); - int newScrollY = (int) (state.scrollY + detector.getFocusY() * scrollFactor); - - mScrollState.set(new ScrollState(scaleFactor, newScrollX, newScrollY)); - onZoomChanged(); - return true; - } - - @Override - public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { - return true; - } - - @Override - public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { - - } - - @Override - public void onZoomAnimated(float scale, int scrollX, int scrollY) { - Rect bounds = computeScrollRange(scale); - - if (scrollX < bounds.left) scrollX = bounds.left; - else if (scrollX > bounds.right) scrollX = bounds.right; - - if (scrollY < bounds.top) scrollY = bounds.top; - else if (scrollY > bounds.bottom) scrollY = bounds.bottom; - - mScrollState.set(new ScrollState(scale, scrollX, scrollY)); - } - - @Override - public void onZoomAnimationFinished() { - onZoomChanged(); - } - - private class GestureListener extends GestureDetector.SimpleOnGestureListener { - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - Rect bounds = getScrollBounds(); - ScrollState state = mScrollState.get(); - mScroller.fling( - state.scrollX, - state.scrollY, - -(int) velocityX, - -(int) velocityY, - bounds.left, - bounds.right, - bounds.top, - bounds.bottom); - awakenScrollBars(mScroller.getDuration()); - return true; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (e2.getPointerCount() != 1) return false; - - Rect bounds = getScrollBounds(); - ScrollState state = mScrollState.get(); - int scrollY = (int) (state.scrollY + distanceY); - int scrollX = (int) (state.scrollX + distanceX); - - if (scrollX < bounds.left) scrollX = bounds.left; - else if (scrollX > bounds.right) scrollX = bounds.right; - - if (scrollY < bounds.top) scrollY = bounds.top; - else if (scrollY > bounds.bottom) scrollY = bounds.bottom; - - mScrollState.set(state.offset(scrollX, scrollY)); - - return super.onScroll(e1, e2, distanceX, distanceY); - } - - @Override - public boolean onDown(MotionEvent e) { - if (e.getPointerCount() == 1) { - mScroller.abortAnimation(); - } - return super.onDown(e); - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - ScaleAnimator animator = new ScaleAnimator(DrawableView.this); - ScrollState state = mScrollState.get(); - if (state.scale == 1f) { - animator.animate( - state.scale, - state.scrollX, - state.scrollY, - 1.6f, - (int) (state.scrollX + e.getX() * 1.6f), - (int) (state.scrollY + e.getY() * 1.6f) - ); - } else { - animator.animate( - state.scale, - state.scrollX, - state.scrollY, - 1f, - (int) (state.scrollX - e.getX() * 1.6f), - (int) (state.scrollY - e.getY() * 1.6f) - ); - } - return super.onDoubleTap(e); - } - } - - - @Override - public void onScrolled(int currentX, int currentY) { - mScrollState.set(mScrollState.get().offset(currentX, currentY)); - } - - public void forceRedraw() { - if (mThread != null) { - mThread.force = true; - } - } - - @Override - public boolean canScrollHorizontally(int direction) { - Rect bounds = getScrollBounds(); - ScrollState state = mScrollState.get(); - if (direction < 0) { - return state.scrollX > bounds.right; - } else if (direction > 0) { - return state.scrollX < bounds.left; - } else return super.canScrollHorizontally(direction); - } - - @Override - public boolean canScrollVertically(int direction) { - Rect bounds = getScrollBounds(); - ScrollState state = mScrollState.get(); - if (direction < 0) { - return state.scrollY > bounds.top; - } else if (direction > 0) { - return state.scrollY < bounds.bottom; - } else return super.canScrollVertically(direction); - } - - private class DrawThread extends Thread { - - private final SurfaceHolder mSurface; - - private int lastOffsetX = 0, lastOffsetY = 0; - private float lastZoom = 0; - boolean force; - - DrawThread(SurfaceHolder surface) { - mSurface = surface; - } - - @Override - public void run() { - Canvas canvas; - while (!interrupted()) { - canvas = null; - try { - // получаем объект Canvas и выполняем отрисовку - ScrollState state = mScrollState.get(); - int offsetX = -state.scrollX; - int offsetY = -state.scrollY; - if (!force && lastOffsetX == offsetX && lastOffsetY == offsetY && lastZoom == state.scale) { - continue; - } - force = false; - canvas = mSurface.lockCanvas(null); - synchronized (mSurface) { - //long time = System.currentTimeMillis(); - onSurfaceDraw(canvas, lastOffsetX - offsetX, lastOffsetY - offsetY, state.scale); - /*time = System.currentTimeMillis() - time; - Log.d("FPS", 1f / time * 1000f + " fps");*/ - - lastOffsetX = offsetX; - lastOffsetY = offsetY; - lastZoom = state.scale; - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (canvas != null) { - // отрисовка выполнена. выводим результат на экран - mSurface.unlockCanvasAndPost(canvas); - } - } - } - } - } - - public boolean smoothScrollBy(int dX, int dY) { - mScroller.forceFinished(true); - Rect bounds = getScrollBounds(); - ScrollState state = mScrollState.get(); - if (dY < 0) { - dY = Math.max(dY, bounds.top - state.scrollY); - } else if (dY > 0) { - dY = Math.min(dY, bounds.bottom - state.scrollY); - } - if (dX < 0) { - dX = Math.max(dX, bounds.left - state.scrollY); - } else if (dX > 0) { - dX = Math.min(dX, bounds.right - state.scrollY); - } - if (dX == 0 && dY == 0) { - return false; - } - mScroller.startScroll( - state.scrollX, - state.scrollY, - dX, - dY, - 800 - ); - return true; - } - - public float getScaleFactor() { - return mScrollState.get().scale; - } - - public int getViewportWidth() { - return mViewport.width(); - } - - public int getViewportHeight() { - return mViewport.height(); - } - - @WorkerThread - protected abstract void onSurfaceDraw(Canvas canvas, int dX, int dY, float zoom); - - protected void onIdle(){} - - protected void onViewportChanged(int oldHeight, int oldWidth, int newHeight, int newWidth){}; - - protected void onZoomChanged(){}; - - @MainThread - protected abstract Rect getScrollBounds(); - - protected abstract Rect computeScrollRange(float scale); -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ImagesPool.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ImagesPool.java deleted file mode 100644 index b5450796..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ImagesPool.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.content.Context; -import android.graphics.Bitmap; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; - -import org.nv95.openmanga.components.reader.PageLoader; -import org.nv95.openmanga.components.reader.PageWrapper; - -import java.util.Collections; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Created by admin on 01.08.17. - */ - -public class ImagesPool { - - private final PagesLruCache mCache; - private final PageLoader mLoader; - private final ChangesListener mListener; - private final ExecutorService mExecutor; - private final Set mDecodeQueue; - private int mBaseWidth = 0; - - public ImagesPool(Context context, ChangesListener listener) { - mExecutor = Executors.newSingleThreadExecutor(); - mLoader = new PageLoader(context); - mListener = listener; - mDecodeQueue = Collections.synchronizedSet(new TreeSet()); - mCache = new PagesLruCache(4); - } - - @Nullable - public PageImage get(int pos) { - PageImage page = mCache.get(pos); - if (page != null && !page.isRecycled()) { - return page; - } - load(pos); - return null; - } - - public void prefetch(final int pos) { - if (!mCache.contains(pos)) { - load(pos); - } - } - - private void load(final int pos) { - PageWrapper pw = mLoader.requestPage(pos); - if (pw != null && pw.isLoaded() && !mDecodeQueue.contains(pos)) { - mDecodeQueue.add(pos); - AsyncBitmapDecoder.decode(pw.getFilename(), new AsyncBitmapDecoder.DecodeCallback() { - @WorkerThread - @Override - public void onBitmapDecoded(@Nullable Bitmap bitmap) { - if (bitmap != null) { - int w = bitmap.getWidth(); - int h = (int)(mBaseWidth / (float)w * bitmap.getHeight()); - mCache.put(pos, new PageImage(Bitmap.createScaledBitmap( - bitmap, - mBaseWidth, - h, - true - ))); - bitmap.recycle(); - mListener.notifyDataSetChanged(); - } - mDecodeQueue.remove(pos); - } - }, mExecutor); - } - } - - public PageLoader getLoader() { - return mLoader; - } - - public void recycle() { - mCache.evictAll(); - } - - public void setBaseWidth(int width) { - if (mBaseWidth != width) { - mCache.evictAll(); - mBaseWidth = width; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/PageImage.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/PageImage.java deleted file mode 100644 index 3b723338..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/PageImage.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.support.annotation.NonNull; - -import org.nv95.openmanga.utils.DecartUtils; - -/** - * Created by admin on 01.08.17. - */ - -public class PageImage { - - @NonNull - private final Bitmap mBitmap; - - public PageImage(@NonNull Bitmap bitmap) { - mBitmap = bitmap; - } - - public int getHeight() { - return mBitmap.getHeight(); - } - - public int getWidth() { - return mBitmap.getWidth(); - } - - public Rect draw(Canvas canvas, Paint paint, int offsetX, int offsetY, Rect viewport, float scale) { - Rect outRect = new Rect( - offsetX, - offsetY, - ((int) (offsetX + (getWidth() * scale))), - (int) (offsetY + (getHeight() * scale)) - ); - DecartUtils.trimRect(outRect, viewport); - if (!isRecycled()) { - Rect inRect = new Rect(outRect); - DecartUtils.translateRect(inRect, -offsetX, -offsetY); - if (scale != 1f) { - DecartUtils.scaleRect(inRect, 1f / scale); - } - canvas.drawBitmap(mBitmap, inRect, outRect, paint); - } - return outRect; - } - - public boolean isRecycled() { - return mBitmap.isRecycled(); - } - - public void recycle() { - mBitmap.recycle(); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/PagesLruCache.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/PagesLruCache.java deleted file mode 100644 index 4073b085..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/PagesLruCache.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.util.LruCache; - -/** - * Created by admin on 02.08.17. - */ - -public class PagesLruCache extends LruCache { - - public PagesLruCache(int maxSize) { - super(maxSize); - } - - @Override - protected void entryRemoved(boolean evicted, Integer key, PageImage oldValue, PageImage newValue) { - oldValue.recycle(); - } - - public boolean contains(Integer key) { - PageImage value = get(key); - return value != null && !value.isRecycled(); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ScaleAnimator.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ScaleAnimator.java deleted file mode 100644 index fb3f3695..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ScaleAnimator.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.animation.Animator; -import android.animation.TypeEvaluator; -import android.animation.ValueAnimator; -import android.support.annotation.Nullable; -import android.view.animation.AccelerateDecelerateInterpolator; - -/** - * Created by admin on 15.08.17. - */ - -public class ScaleAnimator implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { - - private final ZoomCallback mCallback; - @Nullable - private ValueAnimator mAnimator; - - public ScaleAnimator(ZoomCallback callback) { - mCallback = callback; - } - - public void animate(float initialScale, int initialX, int initialY, float targetScale, int targetX, int targetY) { - if (mAnimator != null) { - mAnimator.cancel(); - } - mAnimator = ValueAnimator.ofObject( - new ZoomEvaluator(), - new ZoomState(initialScale, initialX, initialY), - new ZoomState(targetScale, targetX, targetY) - ); - assert mAnimator != null; - mAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); - mAnimator.setDuration(500); - mAnimator.addUpdateListener(this); - mAnimator.addListener(this); - mAnimator.start(); - } - - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - ZoomState curState = (ZoomState) valueAnimator.getAnimatedValue(); - mCallback.onZoomAnimated(curState.scale, curState.offsetX, curState.offsetY); - } - - @Override - public void onAnimationStart(Animator animator) { - - } - - @Override - public void onAnimationEnd(Animator animator) { - mCallback.onZoomAnimationFinished(); - } - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - - public static class ZoomState { - - final float scale; - final int offsetX; - final int offsetY; - - ZoomState(float scale, int offsetX, int offsetY) { - this.scale = scale; - this.offsetX = offsetX; - this.offsetY = offsetY; - } - } - - private static class ZoomEvaluator implements TypeEvaluator { - - @Override - public ZoomState evaluate(float fraction, ZoomState startValue, ZoomState endValue) { - return new ZoomState( - startValue.scale + fraction * (endValue.scale - startValue.scale), - (int)(startValue.offsetX + fraction * (endValue.offsetX - startValue.offsetX)), - (int)(startValue.offsetY + fraction * (endValue.offsetY - startValue.offsetY)) - ); - } - } - - public interface ZoomCallback { - void onZoomAnimated(float scale, int scrollX, int scrollY); - void onZoomAnimationFinished(); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ScrollState.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ScrollState.java deleted file mode 100644 index e4d07807..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/ScrollState.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -/** - * Created by admin on 15.08.17. - */ - -class ScrollState { - - final float scale; - final int scrollX; - final int scrollY; - - ScrollState(float scale, int scrollX, int scrollY) { - this.scale = scale; - this.scrollX = scrollX; - this.scrollY = scrollY; - } - - public ScrollState scale(float newScale) { - return new ScrollState(newScale, scrollX, scrollY); - } - - public ScrollState offsetX(int newOffsetX) { - return new ScrollState(scale, newOffsetX, scrollY); - } - - public ScrollState offsetY(int newOffsetY) { - return new ScrollState(scale, scrollX, newOffsetY); - } - - public ScrollState offset(int newOffsetX, int newOffsetY) { - return new ScrollState(scale, newOffsetX, newOffsetY); - } - - public ScrollState offsetRel(int deltaX, int deltaY) { - return new ScrollState(scale, scrollX + deltaX, scrollY + deltaY); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/WebtoonReader.java b/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/WebtoonReader.java deleted file mode 100644 index ab4a323d..00000000 --- a/app/src/main/java/org/nv95/openmanga/components/reader/webtoon/WebtoonReader.java +++ /dev/null @@ -1,411 +0,0 @@ -package org.nv95.openmanga.components.reader.webtoon; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Message; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import android.util.AttributeSet; -import android.util.SparseIntArray; -import android.view.MotionEvent; - -import org.nv95.openmanga.components.reader.MangaReader; -import org.nv95.openmanga.components.reader.OnOverScrollListener; -import org.nv95.openmanga.components.reader.OverscrollDetector; -import org.nv95.openmanga.components.reader.PageLoadListener; -import org.nv95.openmanga.components.reader.PageLoader; -import org.nv95.openmanga.components.reader.PageWrapper; -import org.nv95.openmanga.components.reader.recyclerpager.RecyclerViewPager; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.utils.DecartUtils; -import org.nv95.openmanga.utils.InternalLinkMovement; -import org.nv95.openmanga.utils.LayoutUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; -import java.util.Vector; - -/** - * Created by admin on 14.08.17. - */ - -public class WebtoonReader extends DrawableView implements MangaReader, PageLoadListener, - ChangesListener, Handler.Callback { - - private static final int MSG_IMGSIZE = 1; - private static final int MSG_PAGE_CHANGED = 2; - - private ImagesPool mPool; - private final Paint mPaint; - private final TreeMap mHeights; - private int mFullHeight = 0; - private final Handler mHandler; - private int mCurrentPage; - private final Vector mPageChangeListeners; - private volatile int mOffsetX = 0, mOffsetY = 0; - private final Rect mScrollBounds; - private int mTopPage, mTopPageOffset; - private volatile boolean mShowNumbers; - private final SparseIntArray mProgressMap; - @Nullable - private OverscrollDetector mOverscrollDetector; - - public WebtoonReader(Context context) { - this(context, null, 0); - } - - public WebtoonReader(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public WebtoonReader(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setHorizontalScrollBarEnabled(false); - setVerticalScrollBarEnabled(true); - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setColor(Color.DKGRAY); - mPaint.setSubpixelText(true); - mPaint.setTextSize(LayoutUtils.DpToPx(getResources(), 18)); - mHeights = new TreeMap<>(); - mProgressMap = new SparseIntArray(); - mHandler = new Handler(this); - mPageChangeListeners = new Vector<>(); - mCurrentPage = 0; - mTopPage = mTopPageOffset = 0; - mScrollBounds = new Rect(); - mOverscrollDetector = null; - } - - @WorkerThread - @Override - protected void onSurfaceDraw(Canvas canvas, int dX, int dY, float zoom) { - canvas.drawColor(Color.LTGRAY); - final Rect viewport = canvas.getClipBounds(); - if (mPool != null) { - mOffsetY -= dY; - mOffsetX -= dX; - int offsetY = mOffsetY; - int page = mCurrentPage; - //draw previous pages - if (offsetY > 0) { - while (offsetY > 0) { - PageImage image = mPool.get(page - 1); - if (image == null) break; - notifyPageHeight(page, image.getHeight()); - offsetY -= image.getHeight() * zoom; - mOffsetY = offsetY; - page--; - notifyPageChanged(mCurrentPage - 1); - } - mPool.prefetch(page-1); - } - //draw current page and next - while (offsetY < canvas.getHeight()) { - PageImage image = mPool.get(page); - if (image == null) break; - notifyPageHeight(page, image.getHeight()); - Rect rect = image.draw(canvas, mPaint, mOffsetX, offsetY, viewport, zoom); - if (rect.bottom <= 0) { - mOffsetY = rect.bottom; - notifyPageChanged(mCurrentPage + 1); - mPool.prefetch(page + 2); - } - page++; - offsetY = rect.bottom; - } - //prefetch next - //mPool.prefetch(page); - - int progress = mProgressMap.get(mCurrentPage, 0); - String text = ""; - if (mShowNumbers) { - text += String.valueOf(mCurrentPage + 1); - if (progress == -1) { - text += " - ERROR"; - } else if (progress != 0 && progress != 100) { - text += " - " + progress + "%"; - } - } else { - if (progress == -1) { - text += "ERROR"; - } else if (progress != 0 && progress != 100) { - text += progress + "%"; - } - } - canvas.drawText(text, 5, canvas.getHeight() - 10, mPaint); - } - } - - private void recomputeWidth() { - int count = mHeights.size(); - if (count == 0) { - mFullHeight = getViewportHeight(); - mTopPageOffset = 0; - return; - } - final int total = getItemCount(); - int sum = 0; - - for (int o : mHeights.values()) { - sum += o; - } - int avgHeight = sum / count; - sum += avgHeight * (total - count); - - mTopPageOffset = 0; - for (int i = 0; i < mTopPage; i++) { - Integer o = mHeights.get(i); - mTopPageOffset += o == null ? avgHeight : o; - } - mFullHeight = sum; - recomputeScrollRange(); - } - - @Override - protected void onViewportChanged(int oldHeight, int oldWidth, int newHeight, int newWidth) { - if (mPool != null) { - mPool.setBaseWidth(newWidth); - } - if (oldHeight != 0) { - mFullHeight = mFullHeight / oldHeight * newHeight; - for (Integer i : mHeights.keySet()) { - mHeights.put(i, mHeights.get(i) / oldHeight * newHeight); - } - } else { - mHeights.clear(); - } - } - - @Override - protected void onZoomChanged() { - recomputeScrollRange(); - } - - private void recomputeScrollRange() { - mScrollBounds.top = 0; - mScrollBounds.left = 0; - mScrollBounds.bottom = mFullHeight; - mScrollBounds.right = getViewportWidth(); - DecartUtils.scaleRect(mScrollBounds, getScaleFactor()); - DecartUtils.translateRect(mScrollBounds, 0, -mTopPageOffset); - mScrollBounds.bottom -= getViewportHeight(); - mScrollBounds.right -= getViewportWidth(); - } - - @Override - protected void onIdle() { - //recomputeWidth(); - } - - @Override - protected Rect getScrollBounds() { - return mScrollBounds; - } - - @Override - public void applyConfig(boolean vertical, boolean reverse, boolean sticky, boolean showNumbers) { - mShowNumbers = showNumbers; - } - - @Override - public boolean scrollToNext(boolean animate) { - return smoothScrollBy(0, (int) (getViewportHeight() * 0.9f)); - } - - @Override - public boolean scrollToPrevious(boolean animate) { - return smoothScrollBy(0, (int) -(getViewportHeight() * 0.9f)); - } - - @Override - public int getCurrentPosition() { - return mCurrentPage; - } - - @Override - public void scrollToPosition(int position) { - int oldPage = mCurrentPage; - mCurrentPage = position; - mOffsetY = 0; - mScrollState.set(mScrollState.get().offsetY(0)); - mTopPage = position; - recomputeWidth(); - forceRedraw(); - for (RecyclerViewPager.OnPageChangedListener o : mPageChangeListeners) { - o.OnPageChanged(oldPage, mCurrentPage); - } - } - - @Override - public void setTapNavs(boolean val) { - - } - - @Override - public void addOnPageChangedListener(RecyclerViewPager.OnPageChangedListener listener) { - mPageChangeListeners.add(listener); - } - - @Override - public void setOnOverScrollListener(OnOverScrollListener listener) { - mOverscrollDetector = new OverscrollDetector(listener); - mOverscrollDetector.setDirections(true, false); - } - - @Override - public boolean isReversed() { - return false; - } - - @Override - public int getItemCount() { - return getLoader().getWrappersList().size(); - } - - @Override - public void initAdapter(Context context, InternalLinkMovement.OnLinkClickListener linkListener) { - mPool = new ImagesPool(context, this); - mPool.getLoader().addListener(this); - } - - @Override - public PageLoader getLoader() { - return mPool.getLoader(); - } - - @Override - public void notifyDataSetChanged() { - recomputeWidth(); - forceRedraw(); - } - - @Override - public PageWrapper getItem(int position) { - return mPool.getLoader().getWrappersList().get(position); - } - - @Override - public void setScaleMode(int scaleMode) { - - } - - @Override - public void reload(int position) { - mPool.getLoader().requestPage(position); - notifyDataSetChanged(); - } - - @Override - public void setPages(List mangaPages) { - mPool.recycle(); - mHeights.clear(); - mProgressMap.clear(); - getLoader().setPages(mangaPages); - scrollToPosition(0); - } - - @Override - public void finish() { - getLoader().cancelAll(); - mPool.recycle(); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mOverscrollDetector != null) { - mOverscrollDetector.onTouch(this, event); - } - return super.onTouchEvent(event); - } - - protected Rect computeScrollRange(float scale) { - Rect range = new Rect(mScrollBounds); - range.bottom = range.top + (int)(mFullHeight * scale) - getViewportHeight(); - range.right = range.left + (int)(getViewportWidth() * scale) - getViewportWidth(); - return range; - } - - @Override - public List getPages() { - List wrappers = mPool.getLoader().getWrappersList(); - ArrayList pages = new ArrayList<>(wrappers.size()); - for (PageWrapper o : wrappers) { - pages.add(o.page); - } - return pages; - } - - @Override - public void onLoadingStarted(PageWrapper page, boolean shadow) { - - } - - @Override - public void onProgressUpdated(PageWrapper page, boolean shadow, int percent) { - mProgressMap.put(page.position, percent); - notifyDataSetChanged(); - } - - @Override - public void onLoadingComplete(PageWrapper page, boolean shadow) { - mProgressMap.put(page.position, 100); - notifyDataSetChanged(); - } - - @Override - public void onLoadingFail(PageWrapper page, boolean shadow) { - mProgressMap.put(page.position, -1); - notifyDataSetChanged(); - } - - @Override - public void onLoadingCancelled(PageWrapper page, boolean shadow) { - mProgressMap.put(page.position, 0); - notifyDataSetChanged(); - } - - @WorkerThread - private void notifyPageHeight(int page, int height) { - Message msg = new Message(); - msg.what = MSG_IMGSIZE; - msg.arg1 = page; - msg.arg2 = height; - mHandler.sendMessage(msg); - } - - @WorkerThread - private void notifyPageChanged(int page) { - Message msg = new Message(); - msg.what = MSG_PAGE_CHANGED; - msg.arg1 = page; - mHandler.sendMessage(msg); - } - - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case MSG_IMGSIZE: - if (!mHeights.containsKey(message.arg1)) { - mHeights.put(message.arg1, message.arg2); - recomputeWidth(); - } - return true; - case MSG_PAGE_CHANGED: - int oldPage = mCurrentPage; - mCurrentPage = message.arg1; - for (RecyclerViewPager.OnPageChangedListener o : mPageChangeListeners) { - o.OnPageChanged(oldPage, mCurrentPage); - } - return true; - default: - return false; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/core/ListWrapper.java b/app/src/main/java/org/nv95/openmanga/core/ListWrapper.java new file mode 100644 index 00000000..a48b2307 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/ListWrapper.java @@ -0,0 +1,33 @@ +package org.nv95.openmanga.core; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +/** + * Created by koitharu on 12.01.18. + */ + +public class ListWrapper extends ObjectWrapper> { + + public ListWrapper(ArrayList object) { + super(object); + } + + public ListWrapper(Throwable error) { + super(error); + } + + public boolean isEmpty() { + return mObject == null || mObject.isEmpty(); + } + + @NonNull + public static ListWrapper badList() { + return new ListWrapper<>(new BadResultException()); + } + + public int size() { + return mObject == null ? 0 : mObject.size(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/MangaLocale.java b/app/src/main/java/org/nv95/openmanga/core/MangaLocale.java new file mode 100644 index 00000000..3ce7c049 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/MangaLocale.java @@ -0,0 +1,23 @@ +package org.nv95.openmanga.core; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by koitharu on 26.12.17. + */ + +@Retention(RetentionPolicy.SOURCE) +@IntDef({MangaLocale.EN, MangaLocale.RU, MangaLocale.JP, MangaLocale.TR, MangaLocale.MULTI, MangaLocale.VIE, MangaLocale.FR}) +public @interface MangaLocale { + + int EN = 0; + int RU = 1; + int JP = 2; + int TR = 3; + int MULTI = 4; + int VIE = 5; + int FR = 6; +} diff --git a/app/src/main/java/org/nv95/openmanga/core/MangaStatus.java b/app/src/main/java/org/nv95/openmanga/core/MangaStatus.java new file mode 100644 index 00000000..a7df8bde --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/MangaStatus.java @@ -0,0 +1,19 @@ +package org.nv95.openmanga.core; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by koitharu on 21.12.17. + */ + +@Retention(RetentionPolicy.SOURCE) +@IntDef({MangaStatus.STATUS_UNKNOWN, MangaStatus.STATUS_COMPLETED, MangaStatus.STATUS_ONGOING}) +public @interface MangaStatus { + + int STATUS_UNKNOWN = 0; + int STATUS_COMPLETED = 1; + int STATUS_ONGOING = 2; +} diff --git a/app/src/main/java/org/nv95/openmanga/core/ObjectWrapper.java b/app/src/main/java/org/nv95/openmanga/core/ObjectWrapper.java new file mode 100644 index 00000000..fedafa51 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/ObjectWrapper.java @@ -0,0 +1,49 @@ +package org.nv95.openmanga.core; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 12.01.18. + */ + +public class ObjectWrapper { + + @Nullable + protected final T mObject; + @Nullable + private final Throwable mThrowable; + + public ObjectWrapper(@NonNull T object) { + mObject = object; + mThrowable = null; + } + + public ObjectWrapper(@NonNull Throwable error) { + mObject = null; + mThrowable = error; + } + + public T get() { + return mObject; + } + + public Throwable getError() { + return mThrowable; + } + + public boolean isSuccess() { + return mThrowable == null; + } + + public boolean isFailed() { + return mThrowable != null; + } + + public static class BadResultException extends Exception {} + + @NonNull + public static ObjectWrapper badObject() { + return new ObjectWrapper<>(new BadResultException()); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/Category.java b/app/src/main/java/org/nv95/openmanga/core/models/Category.java new file mode 100644 index 00000000..5c0509bf --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/Category.java @@ -0,0 +1,53 @@ +package org.nv95.openmanga.core.models; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; + +import org.nv95.openmanga.R; + +/** + * Created by koitharu on 26.12.17. + */ + +public class Category { + + public final int id; + public final String name; + public final long createdAt; + + public Category(int id, String name, long createdAt) { + this.id = id; + this.name = name; + this.createdAt = createdAt; + } + + public Category(String name, long createdAt) { + this.id = name.hashCode(); + this.name = name; + this.createdAt = createdAt; + } + + @NonNull + public static Category createDefault(Context context) { + return new Category(context.getString(R.string.action_favourites), System.currentTimeMillis()); + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(3); + bundle.putInt("id", id); + bundle.putString("name", name); + bundle.putLong("created_at", createdAt); + return bundle; + } + + @NonNull + public static Category fromBundle(Bundle bundle) { + return new Category( + bundle.getInt("id"), + bundle.getString("name"), + bundle.getLong("created_at") + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/FileDesc.java b/app/src/main/java/org/nv95/openmanga/core/models/FileDesc.java new file mode 100644 index 00000000..db961b27 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/FileDesc.java @@ -0,0 +1,14 @@ +package org.nv95.openmanga.core.models; + +import java.io.File; + +public final class FileDesc { + + public final File file; + public final int entryCount; + + public FileDesc(File file, int entryCount) { + this.file = file; + this.entryCount = entryCount; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/ListHeader.java b/app/src/main/java/org/nv95/openmanga/core/models/ListHeader.java new file mode 100644 index 00000000..d1a45fb6 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/ListHeader.java @@ -0,0 +1,30 @@ +package org.nv95.openmanga.core.models; + +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; + +/** + * Created by koitharu on 24.12.17. + */ + +public class ListHeader { + + @StringRes + public final int textResId; + @Nullable + public final String text; + @Nullable + public final Object extra; + + public ListHeader(@Nullable String text, @Nullable Object extra) { + this.text = text; + this.textResId = 0; + this.extra = extra; + } + + public ListHeader(int textResId, @Nullable Object extra) { + this.textResId = textResId; + this.text = null; + this.extra = extra; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaBookmark.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaBookmark.java new file mode 100644 index 00000000..21c98af4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaBookmark.java @@ -0,0 +1,112 @@ +package org.nv95.openmanga.core.models; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 21.12.17. + */ + +public final class MangaBookmark implements Parcelable, UniqueObject { + + public final long id; + public final MangaHeader manga; + public final long chapterId; + public final long pageId; + public final long createdAt; + + public MangaBookmark(long id, MangaHeader manga, long chapterId, long pageId, long createdAt) { + this.id = id; + this.manga = manga; + this.chapterId = chapterId; + this.pageId = pageId; + this.createdAt = createdAt; + } + + public MangaBookmark(MangaHeader manga, long chapterId, long pageId, long createdAt) { + this.id = pageId + 1; //magic + this.manga = manga; + this.chapterId = chapterId; + this.pageId = pageId; + this.createdAt = createdAt; + } + + protected MangaBookmark(Parcel in) { + id = in.readLong(); + manga = new MangaHeader(in); + chapterId = in.readLong(); + pageId = in.readLong(); + createdAt = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MangaBookmark createFromParcel(Parcel in) { + return new MangaBookmark(in); + } + + @Override + public MangaBookmark[] newArray(int size) { + return new MangaBookmark[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + manga.writeToParcel(dest, flags); + dest.writeLong(chapterId); + dest.writeLong(pageId); + dest.writeLong(createdAt); + } + + @Override + public long getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MangaBookmark bookmark = (MangaBookmark) o; + + return id == bookmark.id; + } + + @Override + public int hashCode() { + return (int) (id ^ (id >>> 32)); + } + + public Bundle toBundle() { + final Bundle bundle = new Bundle(5); + bundle.putLong("id", id); + bundle.putBundle("manga", manga.toBundle()); + bundle.putLong("chapter_id", chapterId); + bundle.putLong("page_id", pageId); + bundle.putLong("created_at", createdAt); + return bundle; + } + + @Nullable + public static MangaBookmark from(Bundle bundle) { + if (bundle.containsKey("bookmark")) { + return bundle.getParcelable("bookmark"); + } else return new MangaBookmark( + bundle.getLong("id"), + MangaHeader.from(bundle.getBundle("manga")), + bundle.getLong("chapter_id"), + bundle.getLong("page_id"), + bundle.getLong("created_at") + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaChapter.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaChapter.java new file mode 100644 index 00000000..575962a4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaChapter.java @@ -0,0 +1,104 @@ +package org.nv95.openmanga.core.models; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by koitharu on 21.12.17. + */ + +public class MangaChapter implements Parcelable, UniqueObject { + + public static final int FLAG_CHAPTER_SAVED = 1; + public static final int FLAG_CHAPTER_NEW = 2; + + public final long id; + public final String name; + public final int number; + public final String url; + public final String provider; + + private int mFlags = 0; + + public MangaChapter(String name, int number, String url, String provider) { + this.name = name; + this.number = number; + this.url = url; + this.provider = provider; + this.id = provider.hashCode() + url.hashCode(); + } + + public MangaChapter(long id, String name, int number, String url, String provider) { + this.id = id; + this.name = name; + this.number = number; + this.url = url; + this.provider = provider; + } + + protected MangaChapter(Parcel in) { + id = in.readLong(); + name = in.readString(); + number = in.readInt(); + url = in.readString(); + provider = in.readString(); + + mFlags = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MangaChapter createFromParcel(Parcel in) { + return new MangaChapter(in); + } + + @Override + public MangaChapter[] newArray(int size) { + return new MangaChapter[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(name); + dest.writeInt(number); + dest.writeString(url); + dest.writeString(provider); + + dest.writeInt(mFlags); + } + + public Bundle toBundle() { + Bundle bundle = new Bundle(1); + bundle.putParcelable("_chapter", this); + return bundle; + } + + public static MangaChapter from(Bundle bundle) { + return bundle.getParcelable("_chapter"); + } + + public boolean isSaved() { + return (mFlags & FLAG_CHAPTER_SAVED) != 0; + } + + public void addFlag(int flag) { + mFlags |= flag; + } + + public void removeFlag(int flag) { + mFlags &= ~flag; + } + + @Override + public long getId() { + return id; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaChaptersList.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaChaptersList.java new file mode 100644 index 00000000..0f1a57e7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaChaptersList.java @@ -0,0 +1,100 @@ +package org.nv95.openmanga.core.models; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; + +/** + * Created by koitharu on 21.12.17. + */ + +public class MangaChaptersList extends ArrayList { + + public static final MangaChaptersList EMPTY_LIST = new MangaChaptersList(0); + + public MangaChaptersList() { + } + + public MangaChaptersList(int initialCapacity) { + super(initialCapacity); + } + + public MangaChaptersList(ArrayList source) { + super(source); + } + + @Nullable + public MangaChapter findItemById(long id) { + for (MangaChapter o : this) { + if (o != null && o.id == id) { + return o; + } + } + return null; + } + + @Override + public int indexOf(@Nullable Object obj) { + if (obj instanceof MangaChapter) { + for (int i = 0; i < size(); i++) { + if (get(i).id == ((MangaChapter) obj).id) { + return i; + } + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object obj) { + if (obj instanceof MangaChapter) { + for (int i = size() - 1; i >= 0; i--) { + if (get(i).id == ((MangaChapter) obj).id) { + return i; + } + } + } + return -1; + } + + @NonNull + public MangaChaptersList subListFrom(MangaChapter from, int count) { + int pos = indexOf(from); + if (pos == -1) { + return EMPTY_LIST; + } + final MangaChaptersList list = new MangaChaptersList(count + 1); + int last = Math.min(size() - 1, pos + count); + for (int i = pos; i <= last; i++) { + list.add(get(i)); + } + return list; + } + + @NonNull + public MangaChaptersList subListFrom(MangaChapter from) { + int pos = indexOf(from); + if (pos == -1) { + return EMPTY_LIST; + } + final MangaChaptersList list = new MangaChaptersList(size() - pos); + for (int i = pos; i < size(); i++) { + list.add(get(i)); + } + return list; + } + + @NonNull + public MangaChaptersList subListTo(MangaChapter to) { + int pos = indexOf(to); + if (pos == -1) { + return EMPTY_LIST; + } + final MangaChaptersList list = new MangaChaptersList(pos + 1); + for (int i = 0; i <= pos; i++) { + list.add(get(i)); + } + return list; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaDetails.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaDetails.java new file mode 100644 index 00000000..87d6b6ff --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaDetails.java @@ -0,0 +1,86 @@ +package org.nv95.openmanga.core.models; + +import android.os.Parcel; +import android.support.annotation.NonNull; + +/** + * Created by koitharu on 21.12.17. + */ + +public final class MangaDetails extends MangaHeader { + + public final String description; + public final String cover; + public final String author; + @NonNull + public final MangaChaptersList chapters; + + public MangaDetails(String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, String description, String cover, String author, MangaChaptersList chapters) { + super(name, summary, genres, url, thumbnail, provider, status, rating); + this.description = description; + this.cover = cover; + this.author = author; + this.chapters = chapters; + } + + public MangaDetails(long id, String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, String description, String cover, String author, MangaChaptersList chapters) { + super(id, name, summary, genres, url, thumbnail, provider, status, rating); + this.description = description; + this.cover = cover; + this.author = author; + this.chapters = chapters; + } + + public MangaDetails(MangaHeader header, String description, String cover, String author) { + super(header.id, header.name, header.summary, header.genres, header.url, header.thumbnail, header.provider, header.status, header.rating); + this.description = description; + this.cover = cover; + this.author = author; + this.chapters = new MangaChaptersList(); + } + + protected MangaDetails(Parcel in) { + super(in); + description = in.readString(); + cover = in.readString(); + author = in.readString(); + chapters = new MangaChaptersList(); + in.readTypedList(chapters, MangaChapter.CREATOR); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MangaDetails createFromParcel(Parcel in) { + return new MangaDetails(in); + } + + @Override + public MangaDetails[] newArray(int size) { + return new MangaDetails[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + super.writeToParcel(parcel, i); + parcel.writeString(description); + parcel.writeString(cover); + parcel.writeString(author); + parcel.writeTypedList(chapters); + } + + @NonNull + public static MangaDetails from(SavedManga manga) { + return new MangaDetails( + manga, + manga.description, + manga.thumbnail, + manga.author + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaFavourite.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaFavourite.java new file mode 100644 index 00000000..d4a6230d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaFavourite.java @@ -0,0 +1,40 @@ +package org.nv95.openmanga.core.models; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class MangaFavourite extends MangaHeader { + + public final long createdAt; + public final int categoryId; + public final int totalChapters; + public final int newChapters; + + public MangaFavourite(long id, String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, long createdAt, int categoryId, int totalChapters, int newChapters) { + super(id, name, summary, genres, url, thumbnail, provider, status, rating); + this.createdAt = createdAt; + this.categoryId = categoryId; + this.totalChapters = totalChapters; + this.newChapters = newChapters; + } + + + public static MangaFavourite from(MangaHeader mangaHeader, int categoryId, int totalChapters) { + return new MangaFavourite( + mangaHeader.id, + mangaHeader.name, + mangaHeader.summary, + mangaHeader.genres, + mangaHeader.url, + mangaHeader.thumbnail, + mangaHeader.provider, + mangaHeader.status, + mangaHeader.rating, + System.currentTimeMillis(), + categoryId, + totalChapters, + 0 + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaGenre.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaGenre.java new file mode 100644 index 00000000..76232879 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaGenre.java @@ -0,0 +1,96 @@ +package org.nv95.openmanga.core.models; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; + +/** + * Created by koitharu on 21.12.17. + */ + +public final class MangaGenre implements Parcelable { + + @StringRes + public final int nameId; + @NonNull + public final String value; + + public MangaGenre(int nameId, @NonNull String value) { + this.nameId = nameId; + this.value = value; + } + + private MangaGenre(Parcel in) { + nameId = in.readInt(); + value = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MangaGenre createFromParcel(Parcel in) { + return new MangaGenre(in); + } + + @Override + public MangaGenre[] newArray(int size) { + return new MangaGenre[size]; + } + }; + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeInt(nameId); + parcel.writeString(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MangaGenre that = (MangaGenre) o; + + return nameId == that.nameId && value.equals(that.value); + } + + @Override + public int hashCode() { + return 31 * nameId + value.hashCode(); + } + + @NonNull + public static String joinNames(@NonNull Context context, @NonNull MangaGenre[] genres, @NonNull String delimiter) { + final StringBuilder builder = new StringBuilder(); + boolean nonFirst = false; + for (MangaGenre o: genres) { + if (nonFirst) { + builder.append(delimiter); + } else { + nonFirst = true; + } + builder.append(context.getString(o.nameId)); + } + return builder.toString(); + } + + public static int indexOf(MangaGenre[] array, @NonNull String value) { + for (int i = 0; i < array.length; i++) { + if (value.equalsIgnoreCase(array[i].value)) { + return i; + } + } + return -1; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaHeader.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaHeader.java new file mode 100644 index 00000000..86655316 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaHeader.java @@ -0,0 +1,146 @@ +package org.nv95.openmanga.core.models; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.MangaStatus; + +/** + * Created by koitharu on 21.12.17. + */ + +public class MangaHeader implements Parcelable, UniqueObject { + + public final long id; + public final String name; + public final String summary; + public final String genres; + public final String url; + public final String thumbnail; + public final String provider; + @MangaStatus + public final int status; + public final short rating; //0..100 + + public MangaHeader(String name, String summary, String genres, String url, String thumbnail, String provider, @MangaStatus int status, short rating) { + this.name = name; + this.summary = summary; + this.genres = genres; + this.url = url; + this.thumbnail = thumbnail; + this.provider = provider; + this.status = status; + this.rating = rating; + this.id = provider.hashCode() + url.hashCode(); + } + + public MangaHeader(long id, String name, String summary, String genres, String url, String thumbnail, String provider, @MangaStatus int status, short rating) { + this.id = id; + this.name = name; + this.summary = summary; + this.genres = genres; + this.url = url; + this.thumbnail = thumbnail; + this.provider = provider; + this.status = status; + this.rating = rating; + } + + + protected MangaHeader(Parcel in) { + id = in.readLong(); + name = in.readString(); + summary = in.readString(); + genres = in.readString(); + url = in.readString(); + thumbnail = in.readString(); + provider = in.readString(); + status = in.readInt(); + rating = (short) in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MangaHeader createFromParcel(Parcel in) { + return new MangaHeader(in); + } + + @Override + public MangaHeader[] newArray(int size) { + return new MangaHeader[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeLong(id); + parcel.writeString(name); + parcel.writeString(summary); + parcel.writeString(genres); + parcel.writeString(url); + parcel.writeString(thumbnail); + parcel.writeString(provider); + parcel.writeInt(status); + parcel.writeInt((int) rating); + } + + @NonNull + public static MangaHeader from(MangaHeader other) { + return new MangaHeader( + other.id, + other.name, + other.summary, + other.genres, + other.url, + other.thumbnail, + other.provider, + other.status, + other.rating + ); + } + + @Nullable + public static MangaHeader from(Bundle bundle) { + if (bundle.containsKey("manga")) { + return bundle.getParcelable("manga"); + } else return new MangaHeader( + bundle.getLong("id"), + bundle.getString("name"), + bundle.getString("summary"), + bundle.getString("genres"), + bundle.getString("url"), + bundle.getString("thumbnail"), + bundle.getString("provider"), + bundle.getInt("status"), + (short) bundle.getInt("rating") + ); + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(); + bundle.putLong("id", id); + bundle.putString("name", name); + bundle.putString("summary", summary); + bundle.putString("genres", genres); + bundle.putString("url", url); + bundle.putString("thumbnail", thumbnail); + bundle.putString("provider", provider); + bundle.putInt("status", status); + bundle.putInt("rating", rating); + return bundle; + } + + @Override + public long getId() { + return id; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaHistory.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaHistory.java new file mode 100644 index 00000000..7608adbd --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaHistory.java @@ -0,0 +1,42 @@ +package org.nv95.openmanga.core.models; + +/** + * Created by koitharu on 24.12.17. + */ + +public final class MangaHistory extends MangaHeader { + + public final long chapterId; + public final long pageId; + public final long updatedAt; + public final short readerPreset; + public final int totalChapters; + + public MangaHistory(String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, long chapterId, long pageId, long updatedAt, short readerPreset, int totalChapters) { + super(name, summary, genres, url, thumbnail, provider, status, rating); + this.chapterId = chapterId; + this.pageId = pageId; + this.updatedAt = updatedAt; + this.readerPreset = readerPreset; + this.totalChapters = totalChapters; + } + + public MangaHistory(long id, String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, long chapterId, long pageId, long updatedAt, short readerPreset, int totalChapters) { + super(id, name, summary, genres, url, thumbnail, provider, status, rating); + this.chapterId = chapterId; + this.pageId = pageId; + this.updatedAt = updatedAt; + this.readerPreset = readerPreset; + this.totalChapters = totalChapters; + } + + public MangaHistory(MangaHeader header, MangaChapter chapter, int totalChapters, MangaPage page, short readerPreset) { + super(header.id, header.name, header.summary, header.genres, header.url, header.thumbnail, header.provider, header.status, header.rating); + this.chapterId = chapter.id; + this.totalChapters = totalChapters; + this.pageId = page.id; + updatedAt = System.currentTimeMillis(); + this.readerPreset = readerPreset; + + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaPage.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaPage.java new file mode 100644 index 00000000..fddd38ac --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaPage.java @@ -0,0 +1,65 @@ +package org.nv95.openmanga.core.models; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +/** + * Created by koitharu on 21.12.17. + */ + +public class MangaPage implements Parcelable, UniqueObject { + + public final long id; + @NonNull + public final String url; + public final String provider; + + public MangaPage(String url, String provider) { + this.url = url; + this.provider = provider; + this.id = provider.hashCode() + url.hashCode(); + } + + public MangaPage(long id, @NonNull String url, String provider) { + this.id = id; + this.url = url; + this.provider = provider; + } + + @SuppressWarnings("ConstantConditions") + protected MangaPage(Parcel in) { + id = in.readLong(); + url = in.readString(); + provider = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MangaPage createFromParcel(Parcel in) { + return new MangaPage(in); + } + + @Override + public MangaPage[] newArray(int size) { + return new MangaPage[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(url); + dest.writeString(provider); + } + + @Override + public long getId() { + return id; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaRecommendation.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaRecommendation.java new file mode 100644 index 00000000..0105fed7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaRecommendation.java @@ -0,0 +1,43 @@ +package org.nv95.openmanga.core.models; + +import android.os.Parcel; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class MangaRecommendation extends MangaHeader { + + public final int category; + + public MangaRecommendation(long id, String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, int category) { + super(id, name, summary, genres, url, thumbnail, provider, status, rating); + this.category = category; + } + + protected MangaRecommendation(Parcel in) { + super(in); + category = in.readInt(); + } + + public MangaRecommendation(MangaHeader mangaHeader, int category) { + this( + mangaHeader.id, + mangaHeader.name, + mangaHeader.summary, + mangaHeader.genres, + mangaHeader.url, + mangaHeader.thumbnail, + mangaHeader.provider, + mangaHeader.status, + mangaHeader.rating, + category + ); + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + super.writeToParcel(parcel, i); + parcel.writeInt(category); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/MangaUpdateInfo.java b/app/src/main/java/org/nv95/openmanga/core/models/MangaUpdateInfo.java new file mode 100644 index 00000000..b3178be4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/MangaUpdateInfo.java @@ -0,0 +1,23 @@ +package org.nv95.openmanga.core.models; + +/** + * Created by koitharu on 30.01.18. + */ + +public final class MangaUpdateInfo implements UniqueObject { + + public final long mangaId; + public final String mangaName; + public final int newChapters; + + public MangaUpdateInfo(long mangaId, String mangaName, int newChapters) { + this.mangaId = mangaId; + this.mangaName = mangaName; + this.newChapters = newChapters; + } + + @Override + public long getId() { + return mangaId; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/ProviderHeader.java b/app/src/main/java/org/nv95/openmanga/core/models/ProviderHeader.java new file mode 100644 index 00000000..476c33a8 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/ProviderHeader.java @@ -0,0 +1,30 @@ +package org.nv95.openmanga.core.models; + +import android.support.annotation.NonNull; + +/** + * Created by koitharu on 26.12.17. + */ + +public class ProviderHeader { + + @NonNull + public final String cName; + @NonNull + public final String dName; + + public ProviderHeader(@NonNull String cName, @NonNull String dName) { + this.cName = cName; + this.dName = dName; + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj instanceof ProviderHeader && ((ProviderHeader) obj).cName.equals(cName); + } + + @Override + public int hashCode() { + return cName.hashCode(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/SavedChapter.java b/app/src/main/java/org/nv95/openmanga/core/models/SavedChapter.java new file mode 100644 index 00000000..c7f234f0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/SavedChapter.java @@ -0,0 +1,34 @@ +package org.nv95.openmanga.core.models; + +import android.support.annotation.NonNull; + +/** + * Created by koitharu on 25.01.18. + */ + +public class SavedChapter extends MangaChapter { + + public final long mangaId; + + public SavedChapter(String name, int number, String url, String provider, long mangaId) { + super(name, number, url, provider); + this.mangaId = mangaId; + } + + public SavedChapter(long id, String name, int number, String url, String provider, long mangaId) { + super(id, name, number, url, provider); + this.mangaId = mangaId; + } + + @NonNull + public static SavedChapter from(MangaChapter chapter, long mangaId) { + return new SavedChapter( + chapter.id, + chapter.name, + chapter.number, + chapter.url, + chapter.provider, + mangaId + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/SavedManga.java b/app/src/main/java/org/nv95/openmanga/core/models/SavedManga.java new file mode 100644 index 00000000..deae8829 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/SavedManga.java @@ -0,0 +1,88 @@ +package org.nv95.openmanga.core.models; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import java.io.File; + +/** + * Created by koitharu on 23.01.18. + */ + +public final class SavedManga extends MangaHeader implements Parcelable { + + public final long createdAt; + public final String localPath; + public final String description; + public final String author; + + public SavedManga(String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, long createdAt, String localPath, String description, String author) { + super(name, summary, genres, url, thumbnail, provider, status, rating); + this.createdAt = createdAt; + this.localPath = localPath; + this.description = description; + this.author = author; + } + + public SavedManga(long id, String name, String summary, String genres, String url, String thumbnail, String provider, int status, short rating, long createdAt, String localPath, String description, String author) { + super(id, name, summary, genres, url, thumbnail, provider, status, rating); + this.createdAt = createdAt; + this.localPath = localPath; + this.description = description; + this.author = author; + } + + public static final Creator CREATOR = new Creator() { + @Override + public SavedManga createFromParcel(Parcel in) { + return new SavedManga(in); + } + + @Override + public SavedManga[] newArray(int size) { + return new SavedManga[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + super.writeToParcel(parcel, i); + parcel.writeLong(createdAt); + parcel.writeString(localPath); + parcel.writeString(description); + parcel.writeString(author); + } + + protected SavedManga(Parcel in) { + super(in); + createdAt = in.readLong(); + localPath = in.readString(); + description = in.readString(); + author = in.readString(); + } + + @NonNull + public static SavedManga from(@NonNull MangaDetails other, @NonNull File localPath) { + return new SavedManga( + other.id, + other.name, + other.summary, + other.genres, + other.url, + other.thumbnail, + other.provider, + other.status, + other.rating, + System.currentTimeMillis(), + localPath.getPath(), + other.description, + other.author + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/SavedPage.java b/app/src/main/java/org/nv95/openmanga/core/models/SavedPage.java new file mode 100644 index 00000000..91a02b08 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/SavedPage.java @@ -0,0 +1,36 @@ +package org.nv95.openmanga.core.models; + +import android.support.annotation.NonNull; + +/** + * Created by koitharu on 25.01.18. + */ + +public class SavedPage extends MangaPage { + + public final long chapterId; + public final int number; + + public SavedPage(String url, String provider, long chapterId, int number) { + super(url, provider); + this.chapterId = chapterId; + this.number = number; + } + + public SavedPage(long id, String url, String provider, long chapterId, int number) { + super(id, url, provider); + this.chapterId = chapterId; + this.number = number; + } + + @NonNull + public static SavedPage from(MangaPage page, long chapterId, int number) { + return new SavedPage( + page.id, + page.url, + page.provider, + chapterId, + number + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/TypedString.java b/app/src/main/java/org/nv95/openmanga/core/models/TypedString.java new file mode 100644 index 00000000..944f0a9e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/TypedString.java @@ -0,0 +1,40 @@ +package org.nv95.openmanga.core.models; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; + +public final class TypedString { + + @StringRes + private final int mStringResId; + private final String mString; + private final int mType; + private final int mSubPos; + + public TypedString(@NonNull Context context, @StringRes int stringResId, int type, int subPos) { + mStringResId = stringResId; + mString = context.getString(stringResId); + mType = type; + mSubPos = subPos; + } + + public int getType() { + return mType; + } + + public int getSubPosition() { + return mSubPos; + } + + @NonNull + @Override + public String toString() { + return mString; + } + + @Override + public int hashCode() { + return mStringResId; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/core/models/UniqueObject.java b/app/src/main/java/org/nv95/openmanga/core/models/UniqueObject.java new file mode 100644 index 00000000..26e1a037 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/UniqueObject.java @@ -0,0 +1,10 @@ +package org.nv95.openmanga.core.models; + +/** + * Created by koitharu on 29.01.18. + */ + +public interface UniqueObject { + + long getId(); +} diff --git a/app/src/main/java/org/nv95/openmanga/core/models/UserTip.java b/app/src/main/java/org/nv95/openmanga/core/models/UserTip.java new file mode 100644 index 00000000..92a8073c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/models/UserTip.java @@ -0,0 +1,70 @@ +package org.nv95.openmanga.core.models; + +import android.support.annotation.DrawableRes; +import android.support.annotation.IdRes; +import android.support.annotation.StringRes; + +/** + * Created by koitharu on 12.01.18. + */ + +public final class UserTip { + + public static final int FLAG_NO_DISMISSIBLE = 1; + public static final int FLAG_DISMISS_BUTTON = 2; + + public final String title; + public final String content; + @DrawableRes + public final int icon; + @StringRes + public final int actionText; + @IdRes + public final int actionId; + private int mFlags = 0; + + public UserTip(String title, String content) { + this.title = title; + this.content = content; + this.icon = 0; + this.actionText = 0; + this.actionId = 0; + } + + public UserTip(String title, String content, @DrawableRes int icon) { + this.title = title; + this.content = content; + this.icon = icon; + this.actionText = 0; + this.actionId = 0; + } + + public UserTip(String title, String content, @DrawableRes int icon, @StringRes int actionText, @IdRes int actionId) { + this.title = title; + this.content = content; + this.icon = icon; + this.actionText = actionText; + this.actionId = actionId; + } + + public UserTip addFlag(int flag) { + mFlags |= flag; + return this; + } + + public boolean isDismissible() { + return (mFlags & FLAG_NO_DISMISSIBLE) == 0; + } + + public boolean hasDismissButton() { + return (mFlags & FLAG_DISMISS_BUTTON) != 0; + } + + public boolean hasIcon() { + return icon != 0; + } + + public boolean hasAction() { + return actionText != 0 && actionId != 0; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/CName.java b/app/src/main/java/org/nv95/openmanga/core/providers/CName.java new file mode 100644 index 00000000..4548aceb --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/CName.java @@ -0,0 +1,21 @@ +package org.nv95.openmanga.core.providers; + +import android.support.annotation.StringDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by koitharu on 11.01.18. + */ + +@Retention(RetentionPolicy.SOURCE) +@StringDef({ + DesumeProvider.CNAME, + ExhentaiProvider.CNAME, + ReadmangaruProvider.CNAME, + MintmangaProvider.CNAME, + SelfmangaProvider.CNAME +}) +public @interface CName { +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/DesumeProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/DesumeProvider.java new file mode 100644 index 00000000..3e3fe19b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/DesumeProvider.java @@ -0,0 +1,176 @@ +package org.nv95.openmanga.core.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +/** + * Created by koitharu on 21.12.17. + */ +@SuppressLint("DefaultLocale") +public final class DesumeProvider extends MangaProvider { + + public static final String CNAME = "network/desu.me"; + public static final String DNAME = "DesuMe"; + + private final int[] mSorts = new int[] { + R.string.sort_alphabetical, + R.string.sort_popular, + R.string.sort_updated + }; + + private final String[] mSortValues = new String[] { + "name", + "popular", + "" + }; + + private final MangaGenre[] mGenres = new MangaGenre[]{ + new MangaGenre(R.string.genre_action, "action"), + new MangaGenre(R.string.genre_martialarts, "martial%20arts"), + new MangaGenre(R.string.genre_vampires, "vampire"), + new MangaGenre(R.string.web, "manhwa"), + new MangaGenre(R.string.genre_military, "military"), + new MangaGenre(R.string.genre_harem, "harem"), + new MangaGenre(R.string.genre_youkai, "demons"), + new MangaGenre(R.string.genre_drama, "drama"), + new MangaGenre(R.string.genre_josei, "josei"), + new MangaGenre(R.string.genre_game, "game"), + new MangaGenre(R.string.genre_historical, "historical"), + new MangaGenre(R.string.genre_comedy, "comedy"), + new MangaGenre(R.string.genre_magic, "magic"), + new MangaGenre(R.string.genre_mecha, "mecha"), + new MangaGenre(R.string.genre_mystery, "mystery"), + new MangaGenre(R.string.genre_music, "music"), + new MangaGenre(R.string.genre_sci_fi, "sci-fi"), + new MangaGenre(R.string.genre_parodi, "parody"), + new MangaGenre(R.string.genre_slice_of_life, "slice%20of%20life"), + new MangaGenre(R.string.genre_police, "police"), + new MangaGenre(R.string.genre_adventure, "adventure"), + new MangaGenre(R.string.genre_psychological, "psychological"), + new MangaGenre(R.string.genre_romance, "romance"), + new MangaGenre(R.string.genre_samurai, "samurai"), + new MangaGenre(R.string.genre_supernatural, "supernatural"), + new MangaGenre(R.string.genre_genderbender, "gender%20bender"), + new MangaGenre(R.string.genre_sports, "sports"), + new MangaGenre(R.string.genre_superpower, "super%20power"), + new MangaGenre(R.string.genre_seinen, "seinen"), + new MangaGenre(R.string.genre_shoujo, "shoujo"), + new MangaGenre(R.string.genre_shounen, "shounen"), + new MangaGenre(R.string.genre_shounen_ai, "shounen%20ai"), + new MangaGenre(R.string.genre_thriller, "thriller"), + new MangaGenre(R.string.genre_horror, "horror"), + new MangaGenre(R.string.genre_fantasy, "fantasy"), + new MangaGenre(R.string.genre_hentai, "hentai"), + new MangaGenre(R.string.genre_school, "school"), + new MangaGenre(R.string.genre_ecchi, "ecchi"), + new MangaGenre(R.string.genre_yuri, "yuri"), + new MangaGenre(R.string.genre_yaoi, "yaoi") + }; + + public DesumeProvider(Context context) { + super(context); + } + + @NonNull + @Override + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + String url = String.format( + "http://desu.me/manga/api/?limit=20&order_by=%s&page=%d&genres=%s&search=%s", + sortOrder == -1 ? "popular" : mSortValues[sortOrder], + page + 1, + TextUtils.join(",", genres), + search == null ? "" : search + ); + JSONArray ja = NetworkUtils.getJSONObject(url).getJSONArray("response"); + ArrayList list = new ArrayList<>(ja.length()); + for (int i = 0; i < ja.length(); i++) { + JSONObject jo = ja.getJSONObject(i); + int status = MangaStatus.STATUS_UNKNOWN; + switch (jo.getString("status")) { + case "released": + status = MangaStatus.STATUS_COMPLETED; + break; + case "ongoing": + status = MangaStatus.STATUS_ONGOING; + break; + } + list.add(new MangaHeader( + jo.getString("name"), + jo.getString("russian"), + jo.getString("genres"), + "http://desu.me/manga/api/" + jo.getInt("id"), + jo.getJSONObject("image").getString("x225"), + CNAME, + status, + (byte) (jo.getDouble("score") * 10) + )); + } + return list; + } + + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + JSONObject jo = NetworkUtils.getJSONObject(header.url).getJSONObject("response"); + MangaDetails details = new MangaDetails( + header, + jo.getString("description"), + jo.getJSONObject("image").getString("original"), + "" //not supported by desu.me + ); + JSONArray ja = jo.getJSONObject("chapters").getJSONArray("list"); + final int total = ja.length(); + for (int i = 0; i < total; i++) { + JSONObject chapter = ja.getJSONObject(i); + details.chapters.add(0, new MangaChapter( + chapter.isNull("title") ? "Chapter " + (total - i) : chapter.getString("title"), + i, + details.url + "/chapter/" + chapter.getInt("id"), + CNAME + )); + } + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + JSONObject jo = NetworkUtils.getJSONObject(chapterUrl).getJSONObject("response"); + JSONArray ja = jo.getJSONObject("pages").getJSONArray("list"); + ArrayList pages = new ArrayList<>(ja.length()); + for (int i = 0; i < ja.length(); i++) { + jo = ja.getJSONObject(i); + pages.add(new MangaPage( + jo.getString("img"), + CNAME + )); + } + return pages; + } + + @Override + public MangaGenre[] getAvailableGenres() { + return mGenres; + } + + @Override + public int[] getAvailableSortOrders() { + return mSorts; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/ExhentaiProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/ExhentaiProvider.java new file mode 100644 index 00000000..5bfd5d2f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/ExhentaiProvider.java @@ -0,0 +1,284 @@ +package org.nv95.openmanga.core.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.StringJoinerCompat; +import org.nv95.openmanga.common.utils.network.CookieStore; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by koitharu on 12.01.18. + */ + +public final class ExhentaiProvider extends MangaProvider { + + public static final String CNAME = "network/exhentai"; + public static final String DNAME = "ExHentai"; + + private static final String COOKIE_DEFAULT = "sl=dm_1; nw=1; uconfig=dm_t; igneous=0"; + + static { + CookieStore.getInstance().put("e-hentai.org", COOKIE_DEFAULT); + CookieStore.getInstance().put("exhentai.org", COOKIE_DEFAULT); + } + + private final MangaGenre[] mGenres = new MangaGenre[] { + new MangaGenre(R.string.genre_doujinshi, "f_doujinshi"), + new MangaGenre(R.string.genre_manga, "f_manga"), + new MangaGenre(R.string.genre_artistcg, "f_artistcg"), + new MangaGenre(R.string.genre_gamecg, "f_gamecg"), + new MangaGenre(R.string.genre_western, "f_western"), + new MangaGenre(R.string.genre_nonh, "f_non-h"), + new MangaGenre(R.string.genre_imageset, "f_imageset"), + new MangaGenre(R.string.genre_cosplay, "f_cosplay"), + new MangaGenre(R.string.genre_asianporn, "f_asianporn"), + new MangaGenre(R.string.genre_misc, "f_misc"), + }; + + @NonNull + private String mDomain; + + public ExhentaiProvider(Context context) { + super(context); + final String authCookie = getAuthCookie(); + if (authCookie == null) { + mDomain = "e-hentai.org"; + } else { + mDomain = "exhentai.org"; + CookieStore.getInstance().put(mDomain, authCookie); + } + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + final StringJoinerCompat query = new StringJoinerCompat("&", "", "&"); + for (String g : genres) { + query.add(g + "=1"); + } + if (search != null) { + query.add("f_search=" + search); + } + String url = String.format( + "https://%s/?page=%d&%sf_apply=Apply+Filter", + mDomain, + page, + query.toString() + ); + final Document document = NetworkUtils.getDocument(url); + final Element root = document.body().select("div.itg").first(); + final Elements elements = root.select("div.id1"); + if (elements == null) { + throw new RuntimeException("div.id1 is null"); + } + final ArrayList list = new ArrayList<>(elements.size()); + for (Element o : elements) { + try { + final Element a = o.selectFirst("a"); + final String name = a.text(); + list.add(new MangaHeader( + name.replaceAll("\\[[^\\[,\\]]+]", "").trim(), + getFromBrackets(name), + "", + a.attr("href"), + o.selectFirst("img").attr("src"), + CNAME, + MangaStatus.STATUS_UNKNOWN, + parseRating(o.select("div.id43").first().attr("style")) + )); + } catch (Exception ignored) { + } + } + return list; + } + + @SuppressLint("DefaultLocale") + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + final Element body = NetworkUtils.getDocument(header.url).body(); + final Element taglist = body.getElementById("taglist"); + final StringBuilder description = new StringBuilder(); + final Elements trs = taglist.select("tr"); + String author = ""; + for (Element o : trs) { + final Element td = o.selectFirst("td"); + if (td == null) { + continue; + } + final String title = td.text(); + if (title.startsWith("artist")) { + author = td.nextElementSibling().text(); + continue; + } + description.append("") + .append(title) + .append(" ") + .append(td.nextElementSibling().text()) + .append("
"); + } + String cover = header.thumbnail; + try { + final String pvw = body.getElementById("gd1").child(0).attr("style"); + int p = pvw.indexOf("url(") + 4; + cover = pvw.substring(p, pvw.indexOf(')', p)); + } catch (Exception ignored) { + } + final MangaDetails details = new MangaDetails( + header, + description.toString(), + cover, + author + ); + final Element table = body.selectFirst("table.ptt"); + if (table != null) { + final Elements cells = table.select("td"); + if (cells.size() > 2) { + cells.remove(cells.size() - 1); + cells.remove(0); + for (int i = 0; i < cells.size(); i++) { + final Element a = cells.get(i).selectFirst("a"); + if (a != null) { + details.chapters.add(new MangaChapter( + String.format("%s (%s)", header.name, a.text()), + i, + url("https://" + mDomain, a.attr("href")), + header.provider + )); + } + } + } else { + details.chapters.add(new MangaChapter( + header.name, + 0, + header.url, + header.provider + )); + } + } + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + final ArrayList pages = new ArrayList<>(); + final Element body = NetworkUtils.getDocument(chapterUrl).body(); + final Elements cells = body.select("div.gdtm"); + for (Element cell : cells) { + pages.add(new MangaPage( + url("https://" + mDomain, cell.selectFirst("a").attr("href")), + CNAME + )); + } + return pages; + } + + @NonNull + @Override + public String getImageUrl(MangaPage page) throws Exception { + return url("https://" + mDomain, NetworkUtils.getDocument(page.url).getElementById("img").attr("src")); + } + + @Override + public boolean isAuthorizationSupported() { + return true; + } + + @Nullable + @Override + public String authorize(@NonNull String login, @NonNull String password) throws Exception { + String cookie = NetworkUtils.authorize( + "https://forums.e-hentai.org/index.php?act=Login&CODE=01", + "referer", + "https://forums.e-hentai.org/index.php", + "UserName", + login, + "PassWord", + password, + "CookieDate", + "1" + ); + if (cookie == null || !cookie.contains("ipb_pass_hash")) { + return null; + } + cookie = COOKIE_DEFAULT + "; " + cookie; + mDomain = "exhentai.org"; + setAuthCookie(cookie); + return cookie; + } + + private short parseRating(String r) { + r = r.substring( + r.indexOf(":") + 1, + r.indexOf(";") + ); + String[] a = r.split(" "); + short res; + switch (a[0].trim()) { + case "0px": + res = 90; + break; + case "-16px": + res = 70; + break; + case "-32px": + res = 50; + break; + case "-48px": + res = 30; + break; + case "-64px": + res = 10; + break; + default: + res = 0; + } + if (a.length > 1 && res != 0 && "-1px".equals(a[1])) { + res += 10; + } + return res; + } + + @NonNull + private String getFromBrackets(String src) { + Matcher m = Pattern.compile("\\[[^\\[,\\]]+]").matcher(src); + StringBuilder sb = new StringBuilder(); + String t; + boolean firstTime = true; + while (m.find()) { + t = m.group(0); + t = t.substring(1, t.length() - 2); + if (firstTime) { + firstTime = false; + } else { + sb.append(", "); + } + sb.append(t); + } + return sb.toString(); + } + + @Override + public MangaGenre[] getAvailableGenres() { + return mGenres; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/GroupleMangaProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/GroupleMangaProvider.java new file mode 100644 index 00000000..184fcd27 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/GroupleMangaProvider.java @@ -0,0 +1,178 @@ +package org.nv95.openmanga.core.providers; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.nv95.openmanga.common.StringJoinerCompat; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaChaptersList; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +/** + * Created by koitharu on 26.01.18. + */ + +abstract class GroupleMangaProvider extends MangaProvider { + + public GroupleMangaProvider(Context context) { + super(context); + } + + @NonNull + @Override + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + boolean hasQuery = !TextUtils.isEmpty(search); + boolean multipleGenres = genres.length >= 1; + if (multipleGenres) { + return page != 0 ? EMPTY_HEADERS : advancedSearch(org.nv95.openmanga.common.utils.TextUtils.notNull(search), genres); + } else if (hasQuery) { + return simpleSearch(search, page); + } else { + return getList(page, sortOrder, null); + } + } + + @NonNull + protected abstract ArrayList getList(int page, int sortOrder, @Nullable String genre) throws Exception; + + @NonNull + protected abstract ArrayList simpleSearch(@NonNull String search, int page) throws Exception; + + @NonNull + protected abstract ArrayList advancedSearch(@NonNull String search, @NonNull String[] genres) throws Exception; + + protected final ArrayList parseList(Elements elements, String domain) { + final ArrayList list = new ArrayList<>(elements.size()); + for (Element e : elements) { + if (!e.select(".fa-external-link").isEmpty()) { + continue; + } + final Element title = e.selectFirst("h3").child(0); + final Element rating = e.selectFirst("div.rating"); + final Element tags = e.selectFirst(".tags"); + int status = MangaStatus.STATUS_UNKNOWN; + if (!tags.select(".mangaCompleted").isEmpty()) { + status = MangaStatus.STATUS_COMPLETED; + } else if (!tags.select(".mangaTranslationCompleted").isEmpty()) { + status = MangaStatus.STATUS_COMPLETED; + } + final Element subtitle = e.selectFirst("h4"); + final Element img = e.selectFirst("img.lazy"); + list.add(new MangaHeader( + title.text(), + subtitle == null ? "" : subtitle.text(), + parseGenres(e.select(".element-link"), ""), + url(domain, title.attr("href")), + img == null ? "" : img.attr("data-original"), + getCName(), + status, + rating == null ? 0 : parseRating(rating.attr("title")) + )); + } + return list; + } + + private short parseRating(String title) { + try { + int p = title.indexOf('.'); + return Short.parseShort(title.substring(0, p + 2).replace(".", "")); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + final Document doc = NetworkUtils.getDocument(header.url); + Element root = doc.body().getElementById("mangaBox"); + final Element description = root.selectFirst(".manga-description"); + final Element author = root.selectFirst(".elem_author"); + final MangaDetails details = new MangaDetails( + header.id, + header.name, + header.summary, + parseGenres(root.select(".elem_genre a"),header.genres), + header.url, + header.thumbnail, + header.provider, + header.status, + header.rating, + description == null ? "" : description.html(), + root.selectFirst("div.picture-fotorama").child(0).attr("data-full"), + author == null ? "" : author.child(0).text(), + new MangaChaptersList() + ); + root = root.selectFirst("div.chapters-link"); + if (root == null) { + return details; + } + root = root.selectFirst("tbody"); + final Elements ch = root.select("a"); + final String domain = NetworkUtils.getDomainWithScheme(header.url); + final int len = ch.size(); + for (int i = 0; i < len; i++) { + Element o = ch.get(len - i - 1); + details.chapters.add(new MangaChapter( + o.text(), + i, + url(domain, o.attr("href") + "?mtr=1"), + header.provider + )); + } + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + final Elements scripts = NetworkUtils.getDocument(chapterUrl).select("script"); + final String domain = NetworkUtils.getDomainWithScheme(chapterUrl); + final ArrayList pages = new ArrayList<>(); + for (Element script : scripts) { + String s = script.html(); + int start = s.indexOf("rm_h.init("); + if (start == -1) { + continue; + } + start += 10; + final int p = s.lastIndexOf("]") + 1; + s = s.substring(start, p); + final JSONArray array = new JSONArray(s); + for (int i = 0; i < array.length(); i++) { + JSONArray item = array.getJSONArray(i); + pages.add(new MangaPage( + url(domain, item.getString(1) + item.getString(0) + item.getString(2)), + getCName() + )); + } + return pages; + } + throw new RuntimeException("No reader script found"); + } + + @NonNull + private String parseGenres(@Nullable Elements elements, @NonNull String defValue) { + if (elements == null || elements.isEmpty()) { + return defValue; + } + StringJoinerCompat joiner = new StringJoinerCompat(", "); + for (Element o : elements) { + joiner.add(o.text()); + } + return joiner.toString(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/MangaFoxProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/MangaFoxProvider.java new file mode 100644 index 00000000..8484faa5 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/MangaFoxProvider.java @@ -0,0 +1,230 @@ +package org.nv95.openmanga.core.providers; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.StringJoinerCompat; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.common.utils.network.UrlQueryBuilder; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.providers.MangaProvider; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public final class MangaFoxProvider extends MangaProvider { + + public static final String CNAME = "network/mangafox"; + public static final String DNAME = "MangaFox"; + + private final int[] mSorts = new int[]{ + R.string.sort_updated, + R.string.sort_rating, + R.string.sort_popular, + R.string.sort_alphabetical + }; + + private final String[] mSortValues = new String[]{ + "?latest", + "?rating", + "", + "?az" + }; + + private final String[] mSortValuesAdv = new String[]{ + "last_chapter_time", + "rating", + "views", + "name" + }; + + private final MangaGenre[] mGenres = new MangaGenre[]{ + new MangaGenre(R.string.genre_action, "action"), + new MangaGenre(R.string.genre_adult, "adult"), + new MangaGenre(R.string.genre_adventure, "adventure"), + new MangaGenre(R.string.genre_comedy, "comedy"), + new MangaGenre(R.string.genre_doujinshi, "doujinshi"), + new MangaGenre(R.string.genre_drama, "drama"), + new MangaGenre(R.string.genre_ecchi, "ecchi"), + new MangaGenre(R.string.genre_fantasy, "fantasy"), + new MangaGenre(R.string.genre_genderbender, "gender-bender"), + new MangaGenre(R.string.genre_harem, "harem"), + new MangaGenre(R.string.genre_historical, "historical"), + new MangaGenre(R.string.genre_horror, "horror"), + new MangaGenre(R.string.genre_josei, "josei"), + new MangaGenre(R.string.genre_martialarts, "martial-arts"), + new MangaGenre(R.string.genre_mature, "mature"), + new MangaGenre(R.string.genre_mecha, "mecha"), + new MangaGenre(R.string.genre_mystery, "mystery"), + new MangaGenre(R.string.genre_oneshot, "one-shot"), + new MangaGenre(R.string.genre_psychological, "psychological"), + new MangaGenre(R.string.genre_romance, "romance"), + new MangaGenre(R.string.genre_school, "school-life"), + new MangaGenre(R.string.genre_sci_fi, "sci-fi"), + new MangaGenre(R.string.genre_seinen, "seinen"), + new MangaGenre(R.string.genre_shoujo, "shoujo"), + new MangaGenre(R.string.genre_shoujo_ai, "shoujo-ai"), + new MangaGenre(R.string.genre_shounen, "shounen"), + new MangaGenre(R.string.genre_shounen_ai, "shounen-ai"), + new MangaGenre(R.string.genre_slice_of_life, "slice-of-life"), + new MangaGenre(R.string.genre_smut, "smut"), + new MangaGenre(R.string.genre_sports, "sports"), + new MangaGenre(R.string.genre_supernatural, "supernatural"), + new MangaGenre(R.string.genre_tragedy, "tragedy"), + new MangaGenre(R.string.web, "webtoons"), + new MangaGenre(R.string.genre_yaoi, "yaoi"), + new MangaGenre(R.string.genre_yuri, "yuri"), + + }; + + public MangaFoxProvider(Context context) { + super(context); + } + + @NonNull + @Override + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + boolean hasQuery = !TextUtils.isEmpty(search); + boolean multipleGenres = genres.length > 1; + Element root; + if (multipleGenres || hasQuery) { //advanced search + final UrlQueryBuilder query = new UrlQueryBuilder("http://fanfox.net/search.php"); + query.put("advopts", 1); + query.put("artist", ""); + query.put("artist_method", "cw"); + query.put("author", ""); + query.put("artist_method", "cw"); + for (MangaGenre g : mGenres) { + query.put("genres[" + g.value.replaceAll("-", "+") + "]", CollectionsUtils.contains(genres, g) ? 1 : 0); + } + query.put("is_completed", ""); + query.put("name", urlEncode(search)); + query.put("name_method", "cw"); + query.put("rating", ""); + query.put("rating_method", "eq"); + query.put("released", ""); + query.put("released_method", "eq"); + if (sortOrder != -1) { + query.put("order", "za"); + query.put("sort", mSortValuesAdv[sortOrder]); + } + final Document doc = NetworkUtils.getDocument(query.toString()); + root = doc.body().getElementById("listing").selectFirst("div.left"); + } else { + final String genre = CollectionsUtils.getOrNull(genres, 0); + final Document doc = NetworkUtils.getDocument("http://fanfox.net/directory/" + (TextUtils.isEmpty(genre) ? "" : genre + "/") + + page + ".htm" + (sortOrder == -1 ? mSortValues[0] : mSortValues[sortOrder])); + root = doc.body().getElementById("mangalist"); + } + final Elements elements = root.selectFirst("ul.list").select("li"); + final ArrayList list = new ArrayList<>(elements.size()); + for (Element o : elements) { + try { + final Element a = o.selectFirst("a.title"); + final String name = a.text(); + list.add(new MangaHeader( + name, + "", + o.selectFirst("p.info").attr("title"), + "http:" + a.attr("href"), + o.selectFirst("img").attr("src"), + CNAME, + MangaStatus.STATUS_UNKNOWN, + (short) (Integer.parseInt(o.selectFirst("span.rate").text().replaceAll("[^0-9]", "")) * 2) + )); + } catch (Exception ignored) { + } + } + return list; + } + + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + final Document doc = NetworkUtils.getDocument(header.url); + Element root = doc.body().getElementById("page"); + final Element title = root.getElementById("title"); + String author = ""; + try { + author = title.selectFirst("table").select("tr").get(1).select("td").get(1).text(); + } catch (Exception ignored) { + } + String description = title.selectFirst("p.summary").html(); + final Element warning = root.selectFirst("div.warning"); + if (warning != null) { + description = String.format("%s

", warning.html()) + description; + } + final MangaDetails details = new MangaDetails( + header, + description, + root.selectFirst("div.cover").selectFirst("img").attr("src"), + author + ); + root = root.getElementById("chapters"); + final Elements lis = root.select("li"); + int i = 0; + for (Element li : lis) { + final Element h3 = li.selectFirst("h3"); + if (h3 == null) { + continue; + } + final Element a = h3.selectFirst("a"); + details.chapters.add(new MangaChapter( + h3.text(), + i, + "http:" + a.attr("href"), + header.provider + )); + i++; + } + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + final ArrayList pages = new ArrayList<>(); + final Document document = NetworkUtils.getDocument(chapterUrl); + final String prefix = chapterUrl.substring(0, chapterUrl.lastIndexOf('/') + 1); + final Elements els = document.body().selectFirst("select.m").select("option"); + final Pattern numberPattern = Pattern.compile("[0-9]+"); + for (Element o : els) { + final String val = o.attr("value"); + if (numberPattern.matcher(val).matches()) { + pages.add(new MangaPage( + prefix + val + ".htm", + CNAME + )); + } + } + return pages; + } + + @NonNull + @Override + public String getImageUrl(@NonNull MangaPage page) throws Exception { + return NetworkUtils.getDocument(page.url).getElementById("image").attr("src"); + } + + @Override + public int[] getAvailableSortOrders() { + return mSorts; + } + + @Override + public MangaGenre[] getAvailableGenres() { + return mGenres; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/MangaProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/MangaProvider.java new file mode 100644 index 00000000..d689e380 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/MangaProvider.java @@ -0,0 +1,234 @@ +package org.nv95.openmanga.core.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.util.LruCache; + +import org.nv95.openmanga.common.utils.TextUtils; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by koitharu on 21.12.17. + */ + +public abstract class MangaProvider { + + private static final HashMap sDomainsMap = new HashMap<>(); + protected static final ArrayList EMPTY_HEADERS = new ArrayList<>(0); + + protected final Context mContext; + + public MangaProvider(Context context) { + mContext = context; + } + + protected SharedPreferences getPreferences() { + return mContext.getSharedPreferences("prov_" + this.getCName().replace('/','_'), Context.MODE_PRIVATE); + } + + /** + * + * @param search - search query or null + * @param page - from 0 to infinity + * @param sortOrder - index of {@link #getAvailableSortOrders()} or -1 + * @param genres - array of values from {@link #getAvailableGenres()} + * @return list + * @throws Exception if anything wrong + */ + @NonNull + public abstract ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception; + + @NonNull + public abstract MangaDetails getDetails(MangaHeader header) throws Exception; + + @NonNull + public abstract ArrayList getPages(String chapterUrl) throws Exception; + + @NonNull + public String getImageUrl(@NonNull MangaPage page) throws Exception { + return page.url; + } + + public boolean signIn(String login, String password) throws Exception { + return false; + } + + protected void setAuthCookie(@Nullable String cookie) { + getPreferences().edit() + .putString("_cookie", cookie) + .apply(); + } + + @Nullable + protected String getAuthCookie() { + return getPreferences().getString("_cookie", null); + } + + public boolean isSearchSupported() { + return true; + } + + public boolean isMultipleGenresSupported() { + return true; + } + + public boolean isAuthorizationSupported() { + return false; + } + + @Nullable + @SuppressLint("WrongConstant") + public String authorize(@NonNull String login, @NonNull String password) throws Exception { + throw new UnsupportedOperationException("Authorization not supported for " + getCName()); + } + + public MangaGenre[] getAvailableGenres() { + return new MangaGenre[0]; + } + + public int[] getAvailableSortOrders() { + return new int[0]; + } + + public final boolean isAuthorized() { + return !android.text.TextUtils.isEmpty(getAuthCookie()); + } + + @Nullable + public String getName() { + try { + return ((String)this.getClass().getField("DNAME").get(this)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @CName + @Nullable + public String getCName() { + try { + return ((String)this.getClass().getField("CNAME").get(this)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static LruCache sProviderCache = new LruCache<>(4); + + @NonNull + public static MangaProvider get(Context context, @NonNull @CName String cName) throws AssertionError { + MangaProvider provider = sProviderCache.get(cName); + if (provider != null) { + return NetworkUtils.isNetworkAvailable(context) ? provider : new OfflineMangaProvider(context, provider); + } + switch (cName) { + case DesumeProvider.CNAME: + provider = new DesumeProvider(context); + break; + case ExhentaiProvider.CNAME: + provider = new ExhentaiProvider(context); + break; + case ReadmangaruProvider.CNAME: + provider = new ReadmangaruProvider(context); + break; + case MintmangaProvider.CNAME: + provider = new MintmangaProvider(context); + break; + case SelfmangaProvider.CNAME: + provider = new SelfmangaProvider(context); + break; + case MangarawProvider.CNAME: + provider = new MangarawProvider(context); + break; + case NudeMoonProvider.CNAME: + provider = new NudeMoonProvider(context); + break; + case MangaFoxProvider.CNAME: + provider = new MangaFoxProvider(context); + break; + case ZipArchiveProvider.CNAME: + provider = new ZipArchiveProvider(context); + sProviderCache.put(cName, provider); + return provider; + default: + throw new AssertionError(String.format("Provider %s not registered", cName)); + } + sProviderCache.put(cName, provider); + return NetworkUtils.isNetworkAvailable(context) ? provider : new OfflineMangaProvider(context, provider); + } + + @Nullable + public static MangaGenre findGenre(MangaProvider provider, @StringRes int genreNameRes) { + MangaGenre[] genres = provider.getAvailableGenres(); + for (MangaGenre o : genres) { + if (o.nameId == genreNameRes) { + return o; + } + } + return null; + } + + public static int findSortIndex(MangaProvider provider, @StringRes int sortOrder) { + int[] sorts = provider.getAvailableSortOrders(); + for (int i = 0; i < sorts.length; i++) { + if (sorts[i] == sortOrder) { + return i; + } + } + return -1; + } + + @NonNull + public static String getDomain(@CName String cName) { + if (sDomainsMap.isEmpty()) { + //init + sDomainsMap.put(DesumeProvider.CNAME, "desu.me"); + sDomainsMap.put(ExhentaiProvider.CNAME, "exhentai.org"); + sDomainsMap.put(ReadmangaruProvider.CNAME, "readmanga.me"); + sDomainsMap.put(MintmangaProvider.CNAME, "mintmanga.com"); + sDomainsMap.put(NudeMoonProvider.CNAME, "http://nude-moon.me"); + } + return sDomainsMap.get(cName); + } + + public static SharedPreferences getSharedPreferences(@NonNull Context context, @NonNull @CName String cName) { + return context.getSharedPreferences("prov_" + cName.replace('/','_'), Context.MODE_PRIVATE); + } + + @Nullable + public static String getCookie(@NonNull Context context, @NonNull @CName String cName) { + return getSharedPreferences(context, cName).getString("_cookie", null); + } + + protected static String url(@NonNull String domain, String subj) { + return subj.charAt(0) == '/' ? domain + subj : subj; + } + + @NonNull + static String urlEncode(@Nullable String text) { + if (android.text.TextUtils.isEmpty(text)) { + return ""; + } + try { + return URLEncoder.encode(text, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return text; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/MangarawProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/MangarawProvider.java new file mode 100644 index 00000000..c72d9f7a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/MangarawProvider.java @@ -0,0 +1,98 @@ +package org.nv95.openmanga.core.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaChaptersList; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +/** + * TODO + */ +public final class MangarawProvider extends MangaProvider { + + public static final String CNAME = "network/mangaraw"; + public static final String DNAME = "MangaRaw"; + + public MangarawProvider(Context context) { + super(context); + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + final Element body = NetworkUtils.getDocument(String.format("https://mangaraw.online/manga-list?page=%d", page+1)); + final Elements elements = body.selectFirst(".type-content").select(".media"); + final ArrayList list = new ArrayList<>(elements.size()); + for (Element e : elements) { + final Element a = e.selectFirst("a.thumbnail"); + list.add(new MangaHeader( + e.selectFirst(".chart-title").text(), + "", + "", + url("https://mangaraw.online", a.attr("href")), + url("https://mangaraw.online",a.selectFirst("img").attr("src")), + CNAME, + MangaStatus.STATUS_UNKNOWN, + (short) 0 + )); + } + return list; + } + + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + final Document doc = NetworkUtils.getDocument(header.url); + Element root = doc.body(); + final Element dlh = root.selectFirst(".dl-horizontal"); + final MangaDetails details = new MangaDetails( + header.id, + header.name, + header.summary, + header.genres, + header.url, + header.thumbnail, + header.provider, + header.status, + header.rating, + "", + root.selectFirst(".img-responsive").attr("src"), + "", + new MangaChaptersList() + ); + root = root.selectFirst("ul.chapters"); + final Elements ch = root.select("li h5 a"); + final String domain = "https://mangaraw.online"; + final int len = ch.size(); + for (int i = 0; i < len; i++) { + Element o = ch.get(len - i - 1); + details.chapters.add(new MangaChapter( + o.text(), + i, + url(domain, o.attr("href")), + header.provider + )); + } + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + return null; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/MintmangaProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/MintmangaProvider.java new file mode 100644 index 00000000..c0b66139 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/MintmangaProvider.java @@ -0,0 +1,198 @@ +package org.nv95.openmanga.core.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.StringJoinerCompat; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; + +import java.util.ArrayList; + +/** + * Created by koitharu on 05.02.18. + */ + +public final class MintmangaProvider extends GroupleMangaProvider { + + public static final String CNAME = "network/mint"; + public static final String DNAME = "MintManga"; + + private final int[] mSorts = new int[] { + R.string.sort_popular, + R.string.sort_rating, + R.string.sort_latest, + R.string.sort_updated + }; + + private final String[] mSortValues = new String[] { + "rate", + "votes", + "created", + "updated" + }; + + private final MangaGenre[] mGenres = new MangaGenre[]{ + new MangaGenre(R.string.genre_art, "art"), + new MangaGenre(R.string.genre_bara, "bara"), + new MangaGenre(R.string.genre_action, "action"), + new MangaGenre(R.string.genre_martialarts, "martial_arts"), + new MangaGenre(R.string.genre_vampires, "vampires"), + new MangaGenre(R.string.genre_harem, "harem"), + new MangaGenre(R.string.genre_genderbender, "hender_intriga"), + new MangaGenre(R.string.genre_hero_fantasy, "heroic_fantasy"), + new MangaGenre(R.string.genre_detective, "detective"), + new MangaGenre(R.string.genre_josei, "josei"), + new MangaGenre(R.string.genre_doujinshi, "doujinshi"), + new MangaGenre(R.string.genre_drama, "drama"), + new MangaGenre(R.string.genre_game, "game"), + new MangaGenre(R.string.genre_historical, "historical"), + new MangaGenre(R.string.genre_cyberpunk, "cyberpunk"), + new MangaGenre(R.string.genre_comedy, "comedy"), + new MangaGenre(R.string.genre_mecha, "mecha"), + new MangaGenre(R.string.genre_mystery, "mystery"), + new MangaGenre(R.string.genre_sci_fi, "sci_fi"), + new MangaGenre(R.string.genre_omegaverse, "omegaverse"), + new MangaGenre(R.string.genre_natural, "natural"), + new MangaGenre(R.string.genre_postapocalipse, "postapocalypse"), + new MangaGenre(R.string.genre_adventure, "adventure"), + new MangaGenre(R.string.genre_psychological, "psychological"), + new MangaGenre(R.string.genre_romance, "romance"), + new MangaGenre(R.string.genre_samurai, "samurai"), + new MangaGenre(R.string.genre_supernatural, "supernatural"), + new MangaGenre(R.string.genre_shoujo, "shoujo"), + new MangaGenre(R.string.genre_shoujo_ai, "shoujo_ai"), + new MangaGenre(R.string.genre_shounen, "shounen"), + new MangaGenre(R.string.genre_shounen_ai, "shounen_ai"), + new MangaGenre(R.string.genre_sports, "sport"), + new MangaGenre(R.string.genre_seinen, "seinen"), + new MangaGenre(R.string.genre_tragedy, "tragedy"), + new MangaGenre(R.string.genre_thriller, "thriller"), + new MangaGenre(R.string.genre_horror, "horror"), + new MangaGenre(R.string.genre_fantastic, "fantastic"), + new MangaGenre(R.string.genre_fantasy, "fantasy"), + new MangaGenre(R.string.genre_school, "school"), + new MangaGenre(R.string.genre_erotica, "erotica"), + new MangaGenre(R.string.genre_ecchi, "ecchi"), + new MangaGenre(R.string.genre_yuri, "yuri"), + new MangaGenre(R.string.genre_yaoi, "yaoi") + }; + + private final String[] mTags = new String[] { + "el_2220", + "el_1353", + "el_1346", + "el_1334", + "el_1339", + "el_1333", + "el_1347", + "el_1337", + "el_1343", + "el_1349", + "el_1332", + "el_1310", + "el_5229", + "el_1311", + "el_1351", + "el_1328", + "el_1318", + "el_1324", + "el_1325", + "el_5676", + "el_1327", + "el_1342", + "el_1322", + "el_1335", + "el_1313", + "el_1316", + "el_1350", + "el_1314", + "el_1320", + "el_1326", + "el_1330", + "el_1321", + "el_1329", + "el_1344", + "el_1341", + "el_1317", + "el_1331", + "el_1323", + "el_1319", + "el_1340", + "el_1354", + "el_1315", + "el_1336" + }; + + public MintmangaProvider(Context context) { + super(context); + } + + @NonNull + @SuppressLint("DefaultLocale") + protected ArrayList getList(int page, int sortOrder, @Nullable String genre) throws Exception { + String url = String.format( + "http://mintmanga.com/list%s?lang=&sortType=%s&offset=%d&max=70", + genre == null ? "" : "/genre/" + genre, + sortOrder == -1 ? "rate" : mSortValues[sortOrder], + page * 70 + ); + Document doc = NetworkUtils.getDocument(url); + Element root = doc.body().getElementById("mangaBox").selectFirst("div.tiles"); + return parseList(root.select(".tile"), "http://mintmanga.com/"); + } + + @NonNull + @SuppressLint("DefaultLocale") + protected ArrayList simpleSearch(@NonNull String search, int page) throws Exception { + String url = String.format( + "http://mintmanga.com/search?q=%s&offset=%d&max=50", + search, + page * 50 + ); + Document doc = NetworkUtils.getDocument(url); + Element root = doc.body().getElementById("mangaResults").selectFirst("div.tiles"); + if (root == null) { + return EMPTY_HEADERS; + } + return parseList(root.select(".tile"), "http://mintmanga.com/"); + } + + @NonNull + @SuppressLint("DefaultLocale") + protected ArrayList advancedSearch(@NonNull String search, @NonNull String[] genres) throws Exception { + final StringJoinerCompat query = new StringJoinerCompat("&", "&", ""); + for (String o : genres) { + int i = MangaGenre.indexOf(mGenres, o); + if (i < 0 || i >= mTags.length) { + continue; + } + String tag = mTags[i]; + query.add(tag + "=in"); + } + Document doc = NetworkUtils.getDocument("http://mintmanga.com/search/advanced?q=" + urlEncode(search) + query.toString()); + Element root = doc.body().getElementById("mangaResults").selectFirst("div.tiles"); + return parseList(root.select(".tile"), "http://readmanga.me/"); + } + + @CName + public String getCName() { + return CNAME; + } + + @Override + public MangaGenre[] getAvailableGenres() { + return mGenres; + } + + @Override + public int[] getAvailableSortOrders() { + return mSorts; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/NudeMoonProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/NudeMoonProvider.java new file mode 100644 index 00000000..5a9ae014 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/NudeMoonProvider.java @@ -0,0 +1,146 @@ +package org.nv95.openmanga.core.providers; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaChaptersList; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +public final class NudeMoonProvider extends MangaProvider { + + private static final String BASE_URL = "http://nude-moon.me"; + + public static final String CNAME = "network/nude-moon"; + public static final String DNAME = "Nude-Moon"; + + private final int[] mSorts = new int[] { + R.string.sort_latest, + R.string.sort_popular, + R.string.sort_comments, + R.string.sort_rating + }; + + private final String[] mSortValues = new String[] { + "date", + "views", + "com", + "like" + }; + + public NudeMoonProvider(Context context) { + super(context); + } + + @NonNull + @Override + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + final StringBuilder url = new StringBuilder("http://nude-moon.me/"); + if (!TextUtils.isEmpty(search)) { + if (search.startsWith(":")) { //tags + url.append("tags/").append(TextUtils.join("_", search.substring(1).split("\\s"))).append("+"); + } else { + url.append("search?stext=").append(search); + } + url.append("&").append(sortOrder == -1 ? "date" : mSortValues[sortOrder]); + } else { + url.append("all_manga?").append(sortOrder == -1 ? "date" : mSortValues[sortOrder]); + } + final Document document = NetworkUtils.getDocument(url.toString()); + final Element root = document.body().selectFirst("td.main-bg"); + final Elements elements = root.select("table.news_pic2"); + if (elements == null) { + throw new RuntimeException("td.main-bg > table.news_pic2 is null"); + } + final ArrayList list = new ArrayList<>(elements.size()); + for (Element o : elements) { + try { + final Element a = o.selectFirst(".bg_style1").selectFirst("a"); + final String[] name = a.text().split(" / "); + list.add(new MangaHeader( + name[0], + name.length > 1 ? name[1] : "", + o.getElementById("tags").text(), + url(BASE_URL, a.attr("href")), + url(BASE_URL, o.selectFirst("img.news_pic2").attr("src")), + CNAME, + MangaStatus.STATUS_UNKNOWN, + (short) 0 + )); + } catch (Exception ignored) { + } + } + return list; + } + + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + final Document doc = NetworkUtils.getDocument(header.url); + Element root = doc.body().selectFirst("td.main-body"); + final StringBuilder description = new StringBuilder(); + final Elements titles = root.select("font.darkgreen"); + String author = ""; + for (Element o : titles) { + final String title = o.text(); + if (title.startsWith("Автор")) { + author = o.nextElementSibling().text(); + continue; + } + description.append("") + .append(title) + .append(" ") + .append(o.nextElementSibling().text()) + .append("
"); + } + final MangaDetails details = new MangaDetails( + header, + description.toString(), + url(BASE_URL, root.selectFirst("img.news_pic2").attr("src")), + author + ); + details.chapters.add(new MangaChapter( + header.name, + 0, + url(BASE_URL, root.selectFirst("td.button").getElementsContainingOwnText("Читать онлайн").first().attr("href") + "?row"), + header.provider + )); + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + final ArrayList pages = new ArrayList<>(); + final Element root = NetworkUtils.getDocument(chapterUrl).body().selectFirst("div.square-red"); + final Elements cells = root.select("center"); + for (Element cell : cells) { + final Element img = cell.selectFirst("img"); + if (img != null) { + pages.add(new MangaPage( + url(BASE_URL, img.attr("src")), + CNAME + )); + } + } + return pages; + } + + @Override + public int[] getAvailableSortOrders() { + return mSorts; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/OfflineMangaProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/OfflineMangaProvider.java new file mode 100644 index 00000000..ec9bd4d9 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/OfflineMangaProvider.java @@ -0,0 +1,169 @@ +package org.nv95.openmanga.core.providers; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.models.SavedChapter; +import org.nv95.openmanga.core.models.SavedManga; +import org.nv95.openmanga.core.models.SavedPage; +import org.nv95.openmanga.core.storage.db.SavedChaptersRepository; +import org.nv95.openmanga.core.storage.db.SavedChaptersSpecification; +import org.nv95.openmanga.core.storage.db.SavedMangaRepository; +import org.nv95.openmanga.core.storage.db.SavedPagesRepository; +import org.nv95.openmanga.core.storage.db.SavedPagesSpecification; +import org.nv95.openmanga.core.storage.files.SavedPagesStorage; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by koitharu on 31.01.18. + */ + +public final class OfflineMangaProvider extends MangaProvider { + + private final MangaProvider mDelegate; + + public OfflineMangaProvider(Context context, MangaProvider delegate) { + super(context); + mDelegate = delegate; + } + + @NonNull + @Override + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + return mDelegate.query(search, page, sortOrder, genres); + } + + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + if (NetworkUtils.isNetworkAvailable(mContext)) { + return mDelegate.getDetails(header); + } + final SavedManga savedManga = SavedMangaRepository.get(mContext).find(header); + if (savedManga == null) { + throw new FileNotFoundException(); + } + final MangaDetails details = MangaDetails.from(savedManga); + final ArrayList savedChapters = SavedChaptersRepository.get(mContext) + .query(new SavedChaptersSpecification().manga(header)); + if (savedChapters != null) { + for (SavedChapter o : savedChapters) { + o.addFlag(MangaChapter.FLAG_CHAPTER_SAVED); + details.chapters.add(o); + } + } + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + if (NetworkUtils.isNetworkAvailable(mContext)) { + return mDelegate.getPages(chapterUrl); + } + final SavedPagesRepository repository = SavedPagesRepository.get(mContext); + final SavedChapter chapter = SavedChaptersRepository.get(mContext).findChapterByUrl(chapterUrl); + if (chapter == null) { + throw new RuntimeException("Chapter not found"); + } + final List pages = repository.query(new SavedPagesSpecification(chapter)); + if (pages == null) { + throw new RuntimeException("Failed query saved pages"); + } + final ArrayList result = new ArrayList<>(pages.size()); + final SavedManga savedManga = SavedMangaRepository.get(mContext).get(chapter.mangaId); + if (savedManga == null) { + throw new RuntimeException("Manga not saved!"); + } + final SavedPagesStorage localStorage = new SavedPagesStorage(savedManga); + for (SavedPage o : pages) { + result.add(new MangaPage( + o.id, + "file://" + localStorage.getFile(o).getPath(), + o.provider + )); + } + return result; + } + + @Override + protected SharedPreferences getPreferences() { + return mDelegate.getPreferences(); + } + + @NonNull + @Override + public String getImageUrl(MangaPage page) throws Exception { + return page.url.startsWith("file://") ? page.url : mDelegate.getImageUrl(page); + } + + @Override + public boolean signIn(String login, String password) throws Exception { + return mDelegate.signIn(login, password); + } + + @Override + protected void setAuthCookie(@Nullable String cookie) { + mDelegate.setAuthCookie(cookie); + } + + @Nullable + @Override + protected String getAuthCookie() { + return mDelegate.getAuthCookie(); + } + + @Override + public boolean isSearchSupported() { + return mDelegate.isSearchSupported(); + } + + @Override + public boolean isMultipleGenresSupported() { + return mDelegate.isMultipleGenresSupported(); + } + + @Override + public boolean isAuthorizationSupported() { + return mDelegate.isAuthorizationSupported(); + } + + @Nullable + @Override + public String authorize(@NonNull String login, @NonNull String password) throws Exception { + return mDelegate.authorize(login, password); + } + + @Override + public MangaGenre[] getAvailableGenres() { + return mDelegate.getAvailableGenres(); + } + + @Override + public int[] getAvailableSortOrders() { + return mDelegate.getAvailableSortOrders(); + } + + @Nullable + @Override + public String getCName() { + return mDelegate.getCName(); + } + + @Nullable + @Override + public String getName() { + return mDelegate.getName(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/ReadmangaruProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/ReadmangaruProvider.java new file mode 100644 index 00000000..bba29c1e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/ReadmangaruProvider.java @@ -0,0 +1,197 @@ +package org.nv95.openmanga.core.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.StringJoinerCompat; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; + +import java.util.ArrayList; + +/** + * Created by koitharu on 26.01.18. + */ + +public final class ReadmangaruProvider extends GroupleMangaProvider { + + public static final String CNAME = "network/readmanga.ru"; + public static final String DNAME = "ReadManga"; + + private final int[] mSorts = new int[] { + R.string.sort_popular, + R.string.sort_rating, + R.string.sort_latest, + R.string.sort_updated + }; + + private final String[] mSortValues = new String[] { + "rate", + "votes", + "created", + "updated" + }; + + private final MangaGenre[] mGenres = new MangaGenre[]{ + new MangaGenre(R.string.genre_art, "art"), + new MangaGenre(R.string.genre_action, "action"), + new MangaGenre(R.string.genre_martialarts, "martial_arts"), + new MangaGenre(R.string.genre_vampires, "vampires"), + new MangaGenre(R.string.genre_harem, "harem"), + new MangaGenre(R.string.genre_genderbender, "hender_intriga"), + new MangaGenre(R.string.genre_hero_fantasy, "heroic_fantasy"), + new MangaGenre(R.string.genre_detective, "detective"), + new MangaGenre(R.string.genre_josei, "josei"), + new MangaGenre(R.string.genre_doujinshi, "doujinshi"), + new MangaGenre(R.string.genre_drama, "drama"), + new MangaGenre(R.string.genre_game, "game"), + new MangaGenre(R.string.genre_historical, "historical"), + new MangaGenre(R.string.genre_cyberpunk, "cyberpunk"), + new MangaGenre(R.string.genre_codomo, "codomo"), + new MangaGenre(R.string.genre_comedy, "comedy"), + new MangaGenre(R.string.genre_maho_shoujo, "maho_shoujo"), + new MangaGenre(R.string.genre_mecha, "mecha"), + new MangaGenre(R.string.genre_mystery, "mystery"), + new MangaGenre(R.string.genre_sci_fi, "sci_fi"), + new MangaGenre(R.string.genre_natural, "natural"), + new MangaGenre(R.string.genre_postapocalipse, "postapocalypse"), + new MangaGenre(R.string.genre_adventure, "adventure"), + new MangaGenre(R.string.genre_psychological, "psychological"), + new MangaGenre(R.string.genre_romance, "romance"), + new MangaGenre(R.string.genre_samurai, "samurai"), + new MangaGenre(R.string.genre_supernatural, "supernatural"), + new MangaGenre(R.string.genre_shoujo, "shoujo"), + new MangaGenre(R.string.genre_shoujo_ai, "shoujo_ai"), + new MangaGenre(R.string.genre_shounen, "shounen"), + new MangaGenre(R.string.genre_shounen_ai, "shounen_ai"), + new MangaGenre(R.string.genre_sports, "sport"), + new MangaGenre(R.string.genre_seinen, "seinen"), + new MangaGenre(R.string.genre_tragedy, "tragedy"), + new MangaGenre(R.string.genre_thriller, "thriller"), + new MangaGenre(R.string.genre_horror, "horror"), + new MangaGenre(R.string.genre_fantastic, "fantastic"), + new MangaGenre(R.string.genre_fantasy, "fantasy"), + new MangaGenre(R.string.genre_school, "school"), + new MangaGenre(R.string.genre_ecchi, "ecchi"), + new MangaGenre(R.string.genre_yuri, "yuri") + }; + + private final String[] mTags = new String[] { + "el_5685", + "el_2155", + "el_2143", + "el_2148", + "el_2142", + "el_2156", + "el_2146", + "el_2152", + "el_2158", + "el_2141", + "el_2118", + "el_2154", + "el_2119", + "el_8032", + "el_2137", + "el_2136", + "el_2147", + "el_2126", + "el_2132", + "el_2133", + "el_2135", + "el_2151", + "el_2130", + "el_2144", + "el_2121", + "el_2124", + "el_2159", + "el_2122", + "el_2128", + "el_2134", + "el_2139", + "el_2129", + "el_2138", + "el_2153", + "el_2150", + "el_2125", + "el_2140", + "el_2131", + "el_2127", + "el_2149", + "el_2123" + }; + + public ReadmangaruProvider(Context context) { + super(context); + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + protected ArrayList getList(int page, int sortOrder, @Nullable String genre) throws Exception { + String url = String.format( + "http://readmanga.me/list%s?lang=&sortType=%s&offset=%d&max=70", + genre == null ? "" : "/genre/" + genre, + sortOrder == -1 ? "rate" : mSortValues[sortOrder], + page * 70 + ); + Document doc = NetworkUtils.getDocument(url); + Element root = doc.body().getElementById("mangaBox").selectFirst("div.tiles"); + return parseList(root.select(".tile"), "http://readmanga.me/"); + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + protected ArrayList simpleSearch(@NonNull String search, int page) throws Exception { + final String url = String.format( + "http://readmanga.me/search?q=%s&offset=%d&max=50", + search, + page * 50 + ); //TODO fix "nothing found" problem + final Document doc = NetworkUtils.getDocument(url); + Element root = doc.body().getElementById("mangaResults").selectFirst("div.tiles"); + if (root == null) { + return EMPTY_HEADERS; + } + return parseList(root.select(".tile"), "http://readmanga.me/"); + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + protected ArrayList advancedSearch(@NonNull String search, @NonNull String[] genres) throws Exception { + final StringJoinerCompat query = new StringJoinerCompat("&", "&", ""); + for (String o : genres) { + int i = MangaGenre.indexOf(mGenres, o); + if (i < 0 || i >= mTags.length) { + continue; + } + String tag = mTags[i]; + query.add(tag + "=in"); + } + final Document doc = NetworkUtils.getDocument("http://readmanga.me/search/advanced?q=" + urlEncode(search) + query.toString()); + final Element root = doc.body().getElementById("mangaResults").selectFirst("div.tiles"); + return parseList(root.select(".tile"), "http://readmanga.me/"); + } + + @CName + public String getCName() { + return CNAME; + } + + @Override + public MangaGenre[] getAvailableGenres() { + return mGenres; + } + + @Override + public int[] getAvailableSortOrders() { + return mSorts; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/RelativeMangaProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/RelativeMangaProvider.java new file mode 100644 index 00000000..00fb88e3 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/RelativeMangaProvider.java @@ -0,0 +1,14 @@ +package org.nv95.openmanga.core.providers; + +import org.nv95.openmanga.core.models.MangaHeader; + +import java.util.ArrayList; + +/** + * Created by koitharu on 17.01.18. + */ + +public interface RelativeMangaProvider { + + ArrayList getRelativeManga(MangaHeader manga); +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/SelfmangaProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/SelfmangaProvider.java new file mode 100644 index 00000000..739f746b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/SelfmangaProvider.java @@ -0,0 +1,181 @@ +package org.nv95.openmanga.core.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.StringJoinerCompat; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; + +import java.util.ArrayList; + +public final class SelfmangaProvider extends GroupleMangaProvider { + + public static final String CNAME = "network/selfmanga.ru"; + public static final String DNAME = "SelfManga"; + + private final int[] mSorts = new int[] { + R.string.sort_popular, + R.string.sort_rating, + R.string.sort_latest, + R.string.sort_updated + }; + + private final String[] mSortValues = new String[] { + "rate", + "votes", + "created", + "updated" + }; + + private final MangaGenre[] mGenres = new MangaGenre[]{ + new MangaGenre(R.string.genre_action, "action"), + new MangaGenre(R.string.genre_martialarts, "martial_arts"), + new MangaGenre(R.string.genre_vampires, "vampires"), + new MangaGenre(R.string.genre_harem, "harem"), + new MangaGenre(R.string.genre_genderbender, "hender_intriga"), + new MangaGenre(R.string.genre_hero_fantasy, "heroic_fantasy"), + new MangaGenre(R.string.genre_detective, "detective"), + new MangaGenre(R.string.genre_josei, "josei"), + new MangaGenre(R.string.genre_doujinshi, "doujinshi"), + new MangaGenre(R.string.genre_drama, "drama"), + new MangaGenre(R.string.genre_yonkoma, "yonkoma"), + new MangaGenre(R.string.genre_historical, "historical"), + new MangaGenre(R.string.genre_comedy, "comedy"), + new MangaGenre(R.string.genre_maho_shoujo, "maho_shoujo"), + new MangaGenre(R.string.genre_mystery, "mystery"), + new MangaGenre(R.string.genre_sci_fi, "sci_fi"), + new MangaGenre(R.string.genre_natural, "natural"), + new MangaGenre(R.string.genre_postapocalipse, "postapocalypse"), + new MangaGenre(R.string.genre_adventure, "adventure"), + new MangaGenre(R.string.genre_psychological, "psychological"), + new MangaGenre(R.string.genre_romance, "romance"), + new MangaGenre(R.string.genre_supernatural, "supernatural"), + new MangaGenre(R.string.genre_shoujo, "shoujo"), + new MangaGenre(R.string.genre_shoujo_ai, "shoujo_ai"), + new MangaGenre(R.string.genre_shounen, "shounen"), + new MangaGenre(R.string.genre_shounen_ai, "shounen_ai"), + new MangaGenre(R.string.genre_sports, "sport"), + new MangaGenre(R.string.genre_seinen, "seinen"), + new MangaGenre(R.string.genre_tragedy, "tragedy"), + new MangaGenre(R.string.genre_thriller, "thriller"), + new MangaGenre(R.string.genre_horror, "horror"), + new MangaGenre(R.string.genre_fantastic, "fantastic"), + new MangaGenre(R.string.genre_fantasy, "fantasy"), + new MangaGenre(R.string.genre_school, "school"), + new MangaGenre(R.string.genre_ecchi, "ecchi") + }; + + private final String[] mTags = new String[] { + "el_2155", + "el_2143", + "el_2148", + "el_2142", + "el_2156", + "el_2146", + "el_2152", + "el_2158", + "el_2141", + "el_2118", + "el_2161", + "el_2119", + "el_2136", + "el_2147", + "el_2132", + "el_2133", + "el_2135", + "el_2151", + "el_2130", + "el_2144", + "el_2121", + "el_2159", + "el_2122", + "el_2128", + "el_2134", + "el_2139", + "el_2129", + "el_2138", + "el_2153", + "el_2150", + "el_2125", + "el_2140", + "el_2131", + "el_2127", + "el_4982" + }; + + public SelfmangaProvider(Context context) { + super(context); + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + protected ArrayList getList(int page, int sortOrder, @Nullable String genre) throws Exception { + String url = String.format( + "http://selfmanga.ru/list%s?lang=&sortType=%s&offset=%d&max=70", + genre == null ? "" : "/genre/" + genre, + sortOrder == -1 ? "rate" : mSortValues[sortOrder], + page * 70 + ); + Document doc = NetworkUtils.getDocument(url); + Element root = doc.body().getElementById("mangaBox").selectFirst("div.tiles"); + return parseList(root.select(".tile"), "http://selfmanga.ru/"); + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + protected ArrayList simpleSearch(@NonNull String search, int page) throws Exception { + String url = String.format( + "http://selfmanga.ru/search?q=%s&offset=%d&max=50", + search, + page * 50 + ); + Document doc = NetworkUtils.getDocument(url); + Element root = doc.body().getElementById("mangaResults").selectFirst("div.tiles"); + if (root == null) { + return EMPTY_HEADERS; + } + return parseList(root.select(".tile"), "http://selfmanga.ru/"); + } + + @NonNull + @Override + @SuppressLint("DefaultLocale") + protected ArrayList advancedSearch(@NonNull String search, @NonNull String[] genres) throws Exception { + final StringJoinerCompat query = new StringJoinerCompat("&", "&", ""); + for (String o : genres) { + int i = MangaGenre.indexOf(mGenres, o); + if (i < 0 || i >= mTags.length) { + continue; + } + String tag = mTags[i]; + query.add(tag + "=in"); + } + Document doc = NetworkUtils.getDocument("http://selfmanga.ru/search/advanced?q=" + urlEncode(search) + query.toString()); + Element root = doc.body().getElementById("mangaResults").selectFirst("div.tiles"); + return parseList(root.select(".tile"), "http://selfmanga.ru/"); + } + + @CName + public String getCName() { + return CNAME; + } + + @Override + public MangaGenre[] getAvailableGenres() { + return mGenres; + } + + @Override + public int[] getAvailableSortOrders() { + return mSorts; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/providers/ZipArchiveProvider.java b/app/src/main/java/org/nv95/openmanga/core/providers/ZipArchiveProvider.java new file mode 100644 index 00000000..4d04066e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/providers/ZipArchiveProvider.java @@ -0,0 +1,281 @@ +package org.nv95.openmanga.core.providers; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import android.util.Pair; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.NaturalOrderComparator; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.FilesystemUtils; +import org.nv95.openmanga.common.utils.MetricsUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public final class ZipArchiveProvider extends MangaProvider { + + public static final String CNAME = "storage/zip"; + public static final String DNAME = "Archive"; + public static final String SCHEME = "manga+zip"; + + public ZipArchiveProvider(Context context) { + super(context); + } + + @NonNull + @Override + public ArrayList query(@Nullable String search, int page, int sortOrder, @NonNull String[] genres) throws Exception { + return EMPTY_HEADERS; + } + + @NonNull + @Override + public MangaDetails getDetails(MangaHeader header) throws Exception { + final MangaDetails details = new MangaDetails( + header, + "", + header.thumbnail, + "" + ); + details.chapters.add(new MangaChapter(header.name, 0, header.url, CNAME)); + return details; + } + + @NonNull + @Override + public ArrayList getPages(String chapterUrl) throws Exception { + ZipFile zipFile = null; + final Uri uri = Uri.parse(chapterUrl); + try { + final ArrayList> pagesPairs = new ArrayList<>(); + zipFile = new ZipFile(uri.getPath()); + final Uri zipUri = new Uri.Builder() + .scheme(SCHEME) + .encodedPath(uri.getEncodedPath()) + .build(); + final Enumeration entries = zipFile.entries(); + ZipEntry e; + while (entries.hasMoreElements()) { + e = entries.nextElement(); + if (!e.isDirectory()) { + pagesPairs.add(new Pair<>(e.getName(), new MangaPage( + Uri.withAppendedPath(zipUri, e.getName()).toString(), + CNAME + ))); + } + } + Collections.sort(pagesPairs, new NaturalOrderComparator>() { + @Override + protected String objectToString(Pair obj) { + return obj.first; + } + }); + return CollectionsUtils.mapSeconds(pagesPairs); + } finally { + if (zipFile != null) { + try { + zipFile.close(); + } catch (IOException ignored) { + } + } + } + } + + @NonNull + @Override + public String getName() { + return mContext.getString(R.string.file_archive); + } + + @WorkerThread + @Nullable + public static MangaHeader getManga(@NonNull Context context, @NonNull Uri uri) { + try { + new ZipFile(uri.getPath()).close(); //check if supported format + final File thumbRoot = new File(context.getExternalFilesDir("thumb"), "zip"); + if (!thumbRoot.exists() && !thumbRoot.mkdirs()) { + return null; + } + String thumbUri = ""; + final Bitmap page = getFirstPage(context, uri); + if (page != null) { + try { + final File thumbFile = new File(thumbRoot, String.valueOf(uri.toString().hashCode())); + final MetricsUtils.Size size = MetricsUtils.getPreferredCellSizeMedium(context.getResources()); + final Bitmap thumb = ThumbnailUtils.extractThumbnail(page, size.width, size.height); + page.recycle(); + FileOutputStream outputStream = new FileOutputStream(thumbFile); + thumb.compress(Bitmap.CompressFormat.PNG, 9, outputStream); + outputStream.close(); + thumb.recycle(); + thumbUri = Uri.fromFile(thumbFile).toString(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return new MangaHeader( + FilesystemUtils.getBasename(uri.getLastPathSegment()), + "", + "", + uri.toString(), + thumbUri, + CNAME, + MangaStatus.STATUS_UNKNOWN, + (short) 0 + + ); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Nullable + public static File createThumbnail(@NonNull Context context, @NonNull Uri uri, @NonNull MetricsUtils.Size size) { + File cacheDir = context.getExternalCacheDir(); + if (cacheDir == null) { + cacheDir = context.getCacheDir(); + } + if (cacheDir == null) { + return null; + } + cacheDir = new File(cacheDir, "zipthumbs"); + if (!cacheDir.exists() && !cacheDir.mkdir()) { + return null; + } + final File output = new File(cacheDir, String.valueOf(uri.toString().hashCode())); + if (output.exists()) { + return output; + } + FileOutputStream outputStream = null; + try { + final Bitmap page = getFirstPage(context, uri); + if (page != null) { + final Bitmap thumbnail = ThumbnailUtils.extractThumbnail(page, size.width, size.height); + page.recycle(); + outputStream = new FileOutputStream(output); + thumbnail.compress(Bitmap.CompressFormat.PNG, 9, outputStream); + return output; + } + return null; + } catch (Exception e) { + return null; + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ignored) { + } + } + } + } + + public static boolean isFileSupported(@NonNull File file) { + return isFileSupported(file.getPath()); + } + + public static boolean isFileSupported(@NonNull String filename) { + final String ext = FilesystemUtils.getExtension(filename); + return "cbz".equalsIgnoreCase(ext) || "zip".equalsIgnoreCase(ext); + } + + @Nullable + private static Bitmap getFirstPage(@NonNull Context context, @NonNull Uri uri) { + ZipInputStream zipInputStream = null; + try { + int tries = 4; + zipInputStream = new ZipInputStream(context.getContentResolver().openInputStream(uri)); + ZipEntry entry; + while (tries >= 0 && (entry = zipInputStream.getNextEntry()) != null) { + if (!entry.isDirectory()) { + try { + final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + int len; + final byte[] buffer = new byte[512]; + while ((len = zipInputStream.read(buffer)) > 0) { + bytes.write(buffer, 0, len); + } + return BitmapFactory.decodeByteArray(bytes.toByteArray(), 0, bytes.size()); + } catch (Exception e) { + e.printStackTrace(); + tries--; + } + } + } + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (zipInputStream != null) { + try { + zipInputStream.close(); + } catch (IOException ignored) { + } + } + } + } + + public static void extractTo(String pageUrl, File destination) throws Throwable { + InputStream inputStream = null; + FileOutputStream outputStream = null; + ZipFile zipFile = null; + try { + final Uri uri = Uri.parse(pageUrl); + final String name = uri.getLastPathSegment(); + String path = uri.getPath(); + path = path.substring(0, path.lastIndexOf('/')); + zipFile = new ZipFile(path); + final ZipEntry entry = zipFile.getEntry(name); + inputStream = zipFile.getInputStream(entry); + outputStream = new FileOutputStream(destination); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + outputStream.flush(); + //throw new FileNotFoundException(String.format("Entry %s was not found in archive", name)); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException ignored) { + } + } + if (zipFile != null) { + try { + zipFile.close(); + } catch (IOException ignored) { + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ignored) { + } + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/FlagsStorage.java b/app/src/main/java/org/nv95/openmanga/core/storage/FlagsStorage.java new file mode 100644 index 00000000..517b71ee --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/FlagsStorage.java @@ -0,0 +1,79 @@ +package org.nv95.openmanga.core.storage; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.File; +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class FlagsStorage { + + @Nullable + private static WeakReference sInstanceReference = null; + + private final SharedPreferences mPreferences; + + public static synchronized FlagsStorage get(Context context) { + FlagsStorage instance = sInstanceReference == null ? null : sInstanceReference.get(); + if (instance == null) { + instance = new FlagsStorage(context); + sInstanceReference = new WeakReference<>(instance); + } + return instance; + } + + private FlagsStorage(Context context) { + mPreferences = context.getApplicationContext().getSharedPreferences("flags", Context.MODE_PRIVATE); + } + + public boolean isWizardRequired() { + return mPreferences.getBoolean("wizard_required", true); + } + + public void setWizardRequired(boolean value) { + mPreferences.edit().putBoolean("wizard_required", value).apply(); + } + + public boolean isListDetailed() { + return mPreferences.getBoolean("list_detailed", false); + } + + public void setIsListDetailed(boolean value) { + mPreferences.edit().putBoolean("list_detailed", value).apply(); + } + + public boolean isHistoryDetailed() { + return mPreferences.getBoolean("history_detailed", false); + } + + public void setIsHistoryDetailed(boolean value) { + mPreferences.edit().putBoolean("history_detailed", value).apply(); + } + + public void setLastPickerDir(@NonNull File root) { + mPreferences.edit().putString("picker_root", root.getAbsolutePath()).apply(); + } + + public File getLastPickerRoot(File defValue) { + final String stored = mPreferences.getString("picker_root", null); + if (android.text.TextUtils.isEmpty(stored)) { + return defValue; + } + final File root = new File(stored); + return root.exists() && root.canRead() ? root : defValue; + } + + public boolean isPickerFilterFiles() { + return mPreferences.getBoolean("picker_filter", false); + } + + public void setPickerFilterFiles(boolean value) { + mPreferences.edit().putBoolean("picker_filter", value).apply(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/ProvidersStore.java b/app/src/main/java/org/nv95/openmanga/core/storage/ProvidersStore.java new file mode 100644 index 00000000..8fc6a281 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/ProvidersStore.java @@ -0,0 +1,100 @@ +package org.nv95.openmanga.core.storage; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.SparseBooleanArray; + +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.core.models.ProviderHeader; +import org.nv95.openmanga.core.providers.DesumeProvider; +import org.nv95.openmanga.core.providers.ExhentaiProvider; +import org.nv95.openmanga.core.providers.MangaFoxProvider; +import org.nv95.openmanga.core.providers.MintmangaProvider; +import org.nv95.openmanga.core.providers.NudeMoonProvider; +import org.nv95.openmanga.core.providers.ReadmangaruProvider; +import org.nv95.openmanga.core.providers.SelfmangaProvider; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Created by koitharu on 17.01.18. + */ + +public final class ProvidersStore { + + private static final ProviderHeader[] sProviders = new ProviderHeader[]{ + new ProviderHeader(ReadmangaruProvider.CNAME, ReadmangaruProvider.DNAME), //0 + new ProviderHeader(MintmangaProvider.CNAME, MintmangaProvider.DNAME), //1 + new ProviderHeader(DesumeProvider.CNAME, DesumeProvider.DNAME), //2 + new ProviderHeader(ExhentaiProvider.CNAME, ExhentaiProvider.DNAME), //3 + new ProviderHeader(SelfmangaProvider.CNAME, SelfmangaProvider.DNAME), //4 + new ProviderHeader(NudeMoonProvider.CNAME, NudeMoonProvider.DNAME), //5 + new ProviderHeader(MangaFoxProvider.CNAME, MangaFoxProvider.DNAME), //6 + //new ProviderHeader(MangarawProvider.CNAME, MangarawProvider.DNAME) //7 + }; + + private final SharedPreferences mPreferences; + + public ProvidersStore(Context context) { + mPreferences = context.getSharedPreferences("providers", Context.MODE_PRIVATE); + } + + public ArrayList getAllProvidersSorted() { + final ArrayList list = new ArrayList<>(sProviders.length); + final int[] order = CollectionsUtils.convertToInt(mPreferences.getString("order", "").split("\\|"), -1); + for (int o : order) { + ProviderHeader h = CollectionsUtils.getOrNull(sProviders, o); + if (h != null) { + list.add(h); + } + } + for (ProviderHeader h : sProviders) { + if (!list.contains(h)) { + list.add(h); + } + } + return list; + } + + public ArrayList getUserProviders() { + final ArrayList list = getAllProvidersSorted(); + final int[] disabled = getDisabledIds(); + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + ProviderHeader h = iterator.next(); + if (CollectionsUtils.indexOf(disabled, h.hashCode()) != -1) { + iterator.remove(); + } + } + return list; + } + + public void save(ArrayList providers, SparseBooleanArray enabled) { + final Integer[] order = new Integer[providers.size()]; + for (int i = 0; i < sProviders.length; i++) { + ProviderHeader h = sProviders[i]; + int p = providers.indexOf(h); + if (p != -1) { + order[i] = p; + } + } + final ArrayList disabled = new ArrayList<>(); + for (int i=0;i args = new ArrayList<>(4); + args.add(mRemoved ? "1" : "0"); + if (mMangaId != null) { + args.add(String.valueOf(mMangaId)); + } + if (mChapterId != null) { + args.add(String.valueOf(mChapterId)); + } + return args.toArray(new String[args.size()]); + } + + @Nullable + @Override + public String getOrderBy() { + return mOrderBy; + } + + @Nullable + @Override + public String getLimit() { + return mLimit; + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(5); + bundle.putString("order_by", mOrderBy); + bundle.putString("limit", mLimit); + if (mMangaId != null) { + bundle.putLong("manga_id", mMangaId); + } + if (mChapterId != null) { + bundle.putLong("chapter_id", mChapterId); + } + bundle.putBoolean("removed", mRemoved); + return bundle; + } + + @NonNull + public static BookmarkSpecification from(Bundle bundle) { + final BookmarkSpecification spec = new BookmarkSpecification(); + spec.mOrderBy = bundle.getString("order_by"); + spec.mLimit = bundle.getString("limit"); + if (bundle.containsKey("manga_id")) { + spec.mMangaId = bundle.getLong("manga_id", 0); + } + if (bundle.containsKey("chapter_id")) { + spec.mChapterId = bundle.getLong("chapter_id", 0); + } + spec.mRemoved = bundle.getBoolean("removed"); + return spec; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/BookmarksRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/BookmarksRepository.java new file mode 100644 index 00000000..423d7bf0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/BookmarksRepository.java @@ -0,0 +1,142 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class BookmarksRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "bookmarks"; + private static final String[] PROJECTION = new String[]{ + "id", //0 + "manga_id", //1 + "name", //2 + "summary", //3 + "genres", //4 + "url", //5 + "thumbnail", //6 + "provider", //7 + "status", //8 + "rating", //9 + "chapter_id", //10 + "page_id", //11 + "created_at", //12 + "removed" //13 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + @NonNull + public static BookmarksRepository get(Context context) { + BookmarksRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new BookmarksRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + private BookmarksRepository(Context context) { + super(context); + } + + + @Override + protected void toContentValues(@NonNull MangaBookmark bookmark, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], bookmark.id); + cv.put(PROJECTION[1], bookmark.manga.id); + cv.put(PROJECTION[2], bookmark.manga.name); + cv.put(PROJECTION[3], bookmark.manga.summary); + cv.put(PROJECTION[4], bookmark.manga.genres); + cv.put(PROJECTION[5], bookmark.manga.url); + cv.put(PROJECTION[6], bookmark.manga.thumbnail); + cv.put(PROJECTION[7], bookmark.manga.provider); + cv.put(PROJECTION[8], bookmark.manga.status); + cv.put(PROJECTION[9], bookmark.manga.rating); + cv.put(PROJECTION[10], bookmark.chapterId); + cv.put(PROJECTION[11], bookmark.pageId); + cv.put(PROJECTION[12], bookmark.createdAt); + cv.put(PROJECTION[13], 0); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull MangaBookmark bookmark) { + return bookmark.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + @NonNull + @Override + protected MangaBookmark fromCursor(@NonNull Cursor cursor) { + return new MangaBookmark( + cursor.getLong(0), + new MangaHeader( + cursor.getLong(1), + cursor.getString(2), + cursor.getString(3), + cursor.getString(4), + cursor.getString(5), + cursor.getString(6), + cursor.getString(7), + cursor.getInt(8), + cursor.getShort(9) + ), + cursor.getLong(10), + cursor.getLong(11), + cursor.getLong(12) + ); + } + + @Nullable + public MangaBookmark find(MangaHeader manga, MangaChapter chapter, MangaPage page) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().query( + getTableName(), + getProjection(), + "manga_id = ? AND chapter_id = ? AND page_id = ?", + new String[]{String.valueOf(manga.id), String.valueOf(chapter.id), String.valueOf(page.id)}, + null, + null, + null + ); + if (cursor.moveToFirst()) { + return fromCursor(cursor); + } + return null; + } catch (Exception e) { + return null; + } finally { + if (cursor != null) cursor.close(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/CategoriesRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/CategoriesRepository.java new file mode 100644 index 00000000..a68d389e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/CategoriesRepository.java @@ -0,0 +1,80 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.Category; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class CategoriesRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "categories"; + private static final String[] PROJECTION = new String[]{ + "id", //0 + "name", //1 + "created_at" //2 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + @NonNull + public static CategoriesRepository get(Context context) { + CategoriesRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new CategoriesRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + private CategoriesRepository(Context context) { + super(context); + } + + @Override + protected void toContentValues(@NonNull Category category, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], category.id); + cv.put(PROJECTION[1], category.name); + cv.put(PROJECTION[2], category.createdAt); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull Category category) { + return category.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + @NonNull + @Override + protected Category fromCursor(@NonNull Cursor cursor) { + return new Category( + cursor.getInt(0), + cursor.getString(1), + cursor.getLong(2) + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/CategoriesSpecification.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/CategoriesSpecification.java new file mode 100644 index 00000000..19346d6d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/CategoriesSpecification.java @@ -0,0 +1,53 @@ +package org.nv95.openmanga.core.storage.db; + +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class CategoriesSpecification implements SqlSpecification { + + @Nullable + private String mOrderBy = null; + + public CategoriesSpecification orderByDate(boolean descending) { + mOrderBy = "created_at"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public CategoriesSpecification orderByName(boolean descending) { + mOrderBy = "name"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + @Nullable + @Override + public String getSelection() { + return null; + } + + @Nullable + @Override + public String[] getSelectionArgs() { + return null; + } + + @Nullable + @Override + public String getOrderBy() { + return mOrderBy; + } + + @Nullable + @Override + public String getLimit() { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/FavouritesRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/FavouritesRepository.java new file mode 100644 index 00000000..41b71f6b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/FavouritesRepository.java @@ -0,0 +1,185 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaUpdateInfo; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class FavouritesRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "favourites"; + private static final String[] PROJECTION = new String[]{ + "id", //0 + "name", //1 + "summary", //2 + "genres", //3 + "url", //4 + "thumbnail", //5 + "provider", //6 + "status", //7 + "rating", //8 + "created_at", //9 + "category_id", //10 + "total_chapters", //11 + "new_chapters", //12 + "removed" //13 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + @NonNull + public static FavouritesRepository get(Context context) { + FavouritesRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new FavouritesRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + private FavouritesRepository(Context context) { + super(context); + } + + @Override + protected void toContentValues(@NonNull MangaFavourite mangaFavourite, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], mangaFavourite.id); + cv.put(PROJECTION[1], mangaFavourite.name); + cv.put(PROJECTION[2], mangaFavourite.summary); + cv.put(PROJECTION[3], mangaFavourite.genres); + cv.put(PROJECTION[4], mangaFavourite.url); + cv.put(PROJECTION[5], mangaFavourite.thumbnail); + cv.put(PROJECTION[6], mangaFavourite.provider); + cv.put(PROJECTION[7], mangaFavourite.status); + cv.put(PROJECTION[8], mangaFavourite.rating); + cv.put(PROJECTION[9], mangaFavourite.createdAt); + cv.put(PROJECTION[10], mangaFavourite.categoryId); + cv.put(PROJECTION[11], mangaFavourite.totalChapters); + cv.put(PROJECTION[12], mangaFavourite.newChapters); + //cv.put(PROJECTION[13], 0); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull MangaFavourite mangaFavourite) { + return mangaFavourite.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + @NonNull + @Override + protected MangaFavourite fromCursor(@NonNull Cursor cursor) { + return new MangaFavourite( + cursor.getLong(0), + cursor.getString(1), + cursor.getString(2), + cursor.getString(3), + cursor.getString(4), + cursor.getString(5), + cursor.getString(6), + cursor.getInt(7), + cursor.getShort(8), + cursor.getLong(9), + cursor.getInt(10), + cursor.getInt(11), + cursor.getInt(12) + ); + } + + @Nullable + public MangaFavourite get(MangaHeader mangaHeader) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().query( + TABLE_NAME, + PROJECTION, + "id = ?", + new String[]{String.valueOf(mangaHeader.id)}, + null, + null, + null, + null + ); + if (cursor.moveToFirst()) { + return fromCursor(cursor); + } else { + return null; + } + } catch (Exception e) { + return null; + } finally { + if (cursor != null) cursor.close(); + } + } + + public boolean remove(MangaHeader manga) { + return mStorageHelper.getWritableDatabase() + .delete(TABLE_NAME, "id=?", new String[]{String.valueOf(manga.id)}) > 0; + } + + public boolean putUpdateInfo(MangaUpdateInfo updateInfo) { + try { + final ContentValues cv = new ContentValues(1); + cv.put("new_chapters", updateInfo.newChapters); + return mStorageHelper.getWritableDatabase().update(getTableName(), cv, + "id=?", new String[]{String.valueOf(updateInfo.mangaId)}) > 0; + } catch (Exception e) { + return false; + } + } + + public void setNoUpdates(MangaHeader manga) { + final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); + try { + database.beginTransaction(); + database.rawQuery("UPDATE " + TABLE_NAME + " SET total_chapters = total_chapters + new_chapters WHERE id = ?", new String[]{String.valueOf(manga.id)}); + database.rawQuery("UPDATE " + TABLE_NAME + " SET new_chapters = 0 WHERE id = ?", new String[]{String.valueOf(manga.id)}); + database.setTransactionSuccessful(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + database.endTransaction(); + } + } + + public void clearNewChapters() { + final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); + try { + database.beginTransaction(); + database.rawQuery("UPDATE " + TABLE_NAME + " SET total_chapters = total_chapters + new_chapters", null); + database.rawQuery("UPDATE " + TABLE_NAME + " SET new_chapters = 0", null); + database.setTransactionSuccessful(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + database.endTransaction(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/FavouritesSpecification.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/FavouritesSpecification.java new file mode 100644 index 00000000..b862710b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/FavouritesSpecification.java @@ -0,0 +1,131 @@ +package org.nv95.openmanga.core.storage.db; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.UniqueObject; + +import java.util.ArrayList; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class FavouritesSpecification implements SqlSpecification, UniqueObject { + + @Nullable + private String mOrderBy = null; + @Nullable + private String mLimit = null; + @Nullable + private Integer mCategory = null; + private boolean mOnlyNew = false; + private boolean mRemoved = false; + + public FavouritesSpecification removed(boolean value) { + mRemoved = value; + return this; + } + + public FavouritesSpecification category(int category) { + mCategory = category; + return this; + } + + public FavouritesSpecification onlyWithNewChapters() { + mOnlyNew = true; + return this; + } + + public FavouritesSpecification orderByDate(boolean descending) { + mOrderBy = "created_at"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public FavouritesSpecification orderByName(boolean descending) { + mOrderBy = "name"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public FavouritesSpecification orderByNewChapters() { + mOrderBy = "new_chapters DESC"; + return this; + } + + public FavouritesSpecification limit(int limit) { + mLimit = String.valueOf(limit); + return this; + } + + @Override + public String getSelection() { + final StringBuilder builder = new StringBuilder("removed = ?"); + if (mCategory != null) { + builder.append(" AND category_id = ?"); + } + if (mOnlyNew) { + builder.append(" AND new_chapters > 0"); + } + return builder.toString(); + } + + @Override + public String[] getSelectionArgs() { + ArrayList args = new ArrayList<>(4); + args.add(mRemoved ? "1" : "0"); + if (mCategory != null) { + args.add(String.valueOf(mCategory)); + } + return args.toArray(new String[args.size()]); + } + + @Nullable + @Override + public String getOrderBy() { + return mOrderBy; + } + + @Nullable + @Override + public String getLimit() { + return mLimit; + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(5); + bundle.putString("order_by", mOrderBy); + bundle.putString("limit", mLimit); + if (mCategory != null) { + bundle.putInt("category", mCategory); + } + bundle.putBoolean("only_new", mOnlyNew); + bundle.putBoolean("removed", mRemoved); + return bundle; + } + + @NonNull + public static FavouritesSpecification from(Bundle bundle) { + final FavouritesSpecification spec = new FavouritesSpecification(); + spec.mOrderBy = bundle.getString("order_by"); + spec.mLimit = bundle.getString("limit"); + if (bundle.containsKey("category")) { + spec.mCategory = bundle.getInt("category", 0); + } + spec.mOnlyNew = bundle.getBoolean("only_new"); + spec.mRemoved = bundle.getBoolean("removed"); + return spec; + } + + @Override + public long getId() { + return mCategory == null ? 0 : mCategory; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/HistoryRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/HistoryRepository.java new file mode 100644 index 00000000..a61910ed --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/HistoryRepository.java @@ -0,0 +1,182 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.models.MangaPage; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 24.12.17. + */ + +public class HistoryRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "history"; + private static final String[] PROJECTION = new String[] { + "id", //0 + "name", //1 + "summary", //2 + "genres", //3 + "url", //4 + "thumbnail", //5 + "provider", //6 + "status", //7 + "rating", //8 + "chapter_id", //9 + "page_id", //10 + "updated_at", //11 + "reader_preset", //12 + "total_chapters", //13 + "removed" //14 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + @NonNull + public static HistoryRepository get(Context context) { + HistoryRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new HistoryRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + private HistoryRepository(Context context) { + super(context); + } + + @Override + protected void toContentValues(@NonNull MangaHistory mangaHistory, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], mangaHistory.id); + cv.put(PROJECTION[1], mangaHistory.name); + cv.put(PROJECTION[2], mangaHistory.summary); + cv.put(PROJECTION[3], mangaHistory.genres); + cv.put(PROJECTION[4], mangaHistory.url); + cv.put(PROJECTION[5], mangaHistory.thumbnail); + cv.put(PROJECTION[6], mangaHistory.provider); + cv.put(PROJECTION[7], mangaHistory.status); + cv.put(PROJECTION[8], mangaHistory.rating); + cv.put(PROJECTION[9], mangaHistory.chapterId); + cv.put(PROJECTION[10], mangaHistory.pageId); + cv.put(PROJECTION[11], mangaHistory.updatedAt); + cv.put(PROJECTION[12], mangaHistory.readerPreset); + cv.put(PROJECTION[13], mangaHistory.totalChapters); + //cv.put(PROJECTION[14], 0); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull MangaHistory history) { + return history.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + @NonNull + @Override + protected MangaHistory fromCursor(@NonNull Cursor cursor) { + return new MangaHistory( + cursor.getLong(0), + cursor.getString(1), + cursor.getString(2), + cursor.getString(3), + cursor.getString(4), + cursor.getString(5), + cursor.getString(6), + cursor.getInt(7), + cursor.getShort(8), + cursor.getLong(9), + cursor.getLong(10), + cursor.getLong(11), + cursor.getShort(12), + cursor.getInt(13) + ); + } + + @Nullable + public MangaHistory find(MangaHeader mangaHeader) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().query( + TABLE_NAME, + PROJECTION, + "id = ?", + new String[]{String.valueOf(mangaHeader.id)}, + null, + null, + null, + null + ); + if (cursor.moveToFirst()) { + return fromCursor(cursor); + } + return null; + } catch (Exception e) { + return null; + } finally { + if (cursor != null) cursor.close(); + } + } + + public boolean quickUpdate(MangaHeader manga, MangaChapter chapter, MangaPage page) { + try { + final ContentValues cv = new ContentValues(); + cv.put(PROJECTION[9], chapter.id); + cv.put(PROJECTION[10], page.id); + cv.put(PROJECTION[11], System.currentTimeMillis()); + return mStorageHelper.getWritableDatabase() + .update(TABLE_NAME, cv, + "id=?", new String[]{String.valueOf(manga.id)}) > 0; + } catch (Exception e) { + return false; + } + } + + public short getPreset(MangaDetails manga, short defaultValue) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().query( + TABLE_NAME, + new String[]{PROJECTION[12]}, + "id = ?", + new String[]{String.valueOf(manga.id)}, + null, + null, + null, + null + ); + if (cursor.moveToFirst()) { + return cursor.getShort(0); + } + return defaultValue; + } catch (Exception e) { + return defaultValue; + } finally { + if (cursor != null) cursor.close(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/HistorySpecification.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/HistorySpecification.java new file mode 100644 index 00000000..8834f94e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/HistorySpecification.java @@ -0,0 +1,86 @@ +package org.nv95.openmanga.core.storage.db; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 24.12.17. + */ + +public class HistorySpecification implements SqlSpecification { + + @Nullable + private String mOrderBy = null; + @Nullable + private String mLimit = null; + + private boolean mRemoved = false; + + public HistorySpecification removed(boolean value) { + mRemoved = value; + return this; + } + + public HistorySpecification orderByDate(boolean descending) { + mOrderBy = "updated_at"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public HistorySpecification orderByName(boolean descending) { + mOrderBy = "name"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public HistorySpecification limit(int limit) { + mLimit = String.valueOf(limit); + return this; + } + + @Override + public String getSelection() { + return "removed = ?"; + } + + @Override + public String[] getSelectionArgs() { + return new String[]{ + mRemoved ? "1" : "0" + }; + } + + @Nullable + @Override + public String getOrderBy() { + return mOrderBy; + } + + @Nullable + @Override + public String getLimit() { + return mLimit; + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(3); + bundle.putString("limit", mLimit); + bundle.putString("order_by", mOrderBy); + bundle.putBoolean("removed", mRemoved); + return bundle; + } + + public static HistorySpecification from(Bundle bundle) { + final HistorySpecification specification = new HistorySpecification(); + specification.mLimit = bundle.getString("limit", null); + specification.mOrderBy = bundle.getString("order_by", null); + specification.mRemoved = bundle.getBoolean("removed", false); + return specification; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/RecommendationsRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/RecommendationsRepository.java new file mode 100644 index 00000000..5a1d4121 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/RecommendationsRepository.java @@ -0,0 +1,125 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaRecommendation; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class RecommendationsRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "recommendations"; + private static final String[] PROJECTION = new String[]{ + "id", //0 + "name", //1 + "summary", //2 + "genres", //3 + "url", //4 + "thumbnail", //5 + "provider", //6 + "status", //7 + "rating", //8 + "category" //9 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + @NonNull + public static RecommendationsRepository get(Context context) { + RecommendationsRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new RecommendationsRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + private RecommendationsRepository(Context context) { + super(context); + } + + @Override + protected void toContentValues(@NonNull MangaRecommendation mangaRecommendation, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], mangaRecommendation.id); + cv.put(PROJECTION[1], mangaRecommendation.name); + cv.put(PROJECTION[2], mangaRecommendation.summary); + cv.put(PROJECTION[3], mangaRecommendation.genres); + cv.put(PROJECTION[4], mangaRecommendation.url); + cv.put(PROJECTION[5], mangaRecommendation.thumbnail); + cv.put(PROJECTION[6], mangaRecommendation.provider); + cv.put(PROJECTION[7], mangaRecommendation.status); + cv.put(PROJECTION[8], mangaRecommendation.rating); + cv.put(PROJECTION[9], mangaRecommendation.category); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull MangaRecommendation mangaRecommendation) { + return mangaRecommendation.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + @NonNull + @Override + protected MangaRecommendation fromCursor(@NonNull Cursor cursor) { + return new MangaRecommendation( + cursor.getLong(0), + cursor.getString(1), + cursor.getString(2), + cursor.getString(3), + cursor.getString(4), + cursor.getString(5), + cursor.getString(6), + cursor.getInt(7), + cursor.getShort(8), + cursor.getInt(9) + ); + } + + + @NonNull + public ArrayList getCategories() { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().rawQuery("SELECT category FROM " + getTableName() + " GROUP BY category", null); + final ArrayList list = new ArrayList<>(cursor.getCount()); + if (cursor.moveToFirst()) { + do { + list.add(cursor.getInt(0)); + } while (cursor.moveToNext()); + } + return list; + } catch (Exception e) { + e.printStackTrace(); + return new ArrayList<>(0); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/RecommendationsSpecifications.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/RecommendationsSpecifications.java new file mode 100644 index 00000000..b5bcb6fb --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/RecommendationsSpecifications.java @@ -0,0 +1,90 @@ +package org.nv95.openmanga.core.storage.db; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.UniqueObject; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class RecommendationsSpecifications implements SqlSpecification, UniqueObject { + + @Nullable + private String mOrderBy = null; + @Nullable + private String mLimit = null; + @Nullable + private Integer mCategory = null; + + public RecommendationsSpecifications orderByName(boolean descending) { + mOrderBy = "name"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public RecommendationsSpecifications orderByRand() { + mOrderBy = "RANDOM()"; + return this; + } + + public RecommendationsSpecifications category(@Nullable Integer category) { + mCategory = category; + return this; + } + + @Override + public long getId() { + return mCategory == null ? 0 : mCategory; + } + + @Nullable + @Override + public String getSelection() { + return mCategory == null ? null : "category = ?"; + } + + @Nullable + @Override + public String[] getSelectionArgs() { + return mCategory == null ? null : new String[]{String.valueOf(mCategory)}; + } + + @Nullable + @Override + public String getOrderBy() { + return mOrderBy; + } + + @Nullable + @Override + public String getLimit() { + return mLimit; + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(5); + bundle.putString("order_by", mOrderBy); + bundle.putString("limit", mLimit); + if (mCategory != null) { + bundle.putInt("category", mCategory); + } + return bundle; + } + + @NonNull + public static RecommendationsSpecifications from(Bundle bundle) { + final RecommendationsSpecifications spec = new RecommendationsSpecifications(); + spec.mOrderBy = bundle.getString("order_by"); + spec.mLimit = bundle.getString("limit"); + if (bundle.containsKey("category")) { + spec.mCategory = bundle.getInt("category", 0); + } + return spec; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/Repository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/Repository.java new file mode 100644 index 00000000..549faa30 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/Repository.java @@ -0,0 +1,22 @@ +package org.nv95.openmanga.core.storage.db; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; + +/** + * Created by koitharu on 24.12.17. + */ + +public interface Repository { + + boolean add(@NonNull T t); + boolean remove(@NonNull T t); + boolean update(@NonNull T t); + void clear(); + boolean contains(@NonNull T t); + + @Nullable + List query(@NonNull SqlSpecification specification); +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SQLiteRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SQLiteRepository.java new file mode 100644 index 00000000..a2fd60fd --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SQLiteRepository.java @@ -0,0 +1,179 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; + +/** + * Created by koitharu on 23.01.18. + */ + +abstract class SQLiteRepository implements Repository { + + protected final StorageHelper mStorageHelper; + + protected SQLiteRepository(Context context) { + mStorageHelper = new StorageHelper(context); + } + + @Override + public boolean add(@NonNull T t) { + try { + final ContentValues cv = new ContentValues(getProjection().length); + toContentValues(t, cv); + return mStorageHelper.getWritableDatabase() + .insert(getTableName(), null, cv) >= 0; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean remove(@NonNull T t) { + return mStorageHelper.getWritableDatabase() + .delete(getTableName(), "id=?", new String[]{String.valueOf(getId(t))}) >= 0; + } + + @Override + public boolean update(@NonNull T t) { + try { + final ContentValues cv = new ContentValues(getProjection().length); + toContentValues(t, cv); + return mStorageHelper.getWritableDatabase().update(getTableName(), cv, + "id=?", new String[]{String.valueOf(getId(t))}) > 0; + } catch (Exception e) { + return false; + } + } + + public boolean addOrUpdate(@NonNull T t) { + final ContentValues cv = new ContentValues(getProjection().length); + toContentValues(t, cv); + final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); + try { + if(database.insert(getTableName(), null, cv) >= 0) { + return true; + } + } catch (Exception ignored) { + } + try { + if(database.update(getTableName(), cv,"id=?", new String[]{String.valueOf(getId(t))}) > 0) { + return true; + } + } catch (Exception ignored) { + } + return false; + } + + public boolean updateOrAdd(@NonNull T t) { + final ContentValues cv = new ContentValues(getProjection().length); + toContentValues(t, cv); + final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); + try { + if(database.update(getTableName(), cv,"id=?", new String[]{String.valueOf(getId(t))}) > 0) { + return true; + } + } catch (Exception e) { + } + try { + if(database.insert(getTableName(), null, cv) >= 0) { + return true; + } + } catch (Exception ignored) { + } + return false; + } + + @Override + public void clear() { + mStorageHelper.getWritableDatabase().delete(getTableName(), null, null); + } + + @Override + public boolean contains(@NonNull T t) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().rawQuery("SELECT * FROM " + getTableName() + " WHERE id = ?", new String[]{String.valueOf(getId(t))}); + return cursor.getCount() > 0; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + @Nullable + @Override + public ArrayList query(@NonNull SqlSpecification specification) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().query( + getTableName(), + getProjection(), + specification.getSelection(), + specification.getSelectionArgs(), + null, + null, + specification.getOrderBy(), + specification.getLimit() + ); + ArrayList list = new ArrayList<>(); + if (cursor.moveToFirst()) { + do { + list.add(fromCursor(cursor)); + } while (cursor.moveToNext()); + } + return list; + } catch (Exception e) { + return null; + } finally { + if (cursor != null) cursor.close(); + } + } + + @Nullable + protected T findById(@NonNull Object id) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().query( + getTableName(), + getProjection(), + "id = ?", + new String[]{String.valueOf(id)}, + null, + null, + null + ); + if (cursor.moveToFirst()) { + return fromCursor(cursor); + } + return null; + } catch (Exception e) { + return null; + } finally { + if (cursor != null) cursor.close(); + } + } + + protected abstract void toContentValues(@NonNull T t, @NonNull ContentValues cv); + + @NonNull + protected abstract String getTableName(); + + @NonNull + protected abstract Object getId(@NonNull T t); + + @NonNull + protected abstract String[] getProjection(); + + @NonNull + protected abstract T fromCursor(@NonNull Cursor cursor); +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedChaptersRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedChaptersRepository.java new file mode 100644 index 00000000..84756023 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedChaptersRepository.java @@ -0,0 +1,131 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.SavedChapter; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 23.01.18. + */ + +public final class SavedChaptersRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "saved_chapters"; + private static final String[] PROJECTION = new String[]{ + "id", //0 + "name", //1 + "number", //2 + "url", //3 + "provider", //4 + "manga_id" //5 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + @NonNull + public static synchronized SavedChaptersRepository get(Context context) { + SavedChaptersRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new SavedChaptersRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + private SavedChaptersRepository(Context context) { + super(context); + } + + @Override + protected void toContentValues(@NonNull SavedChapter chapter, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], chapter.id); + cv.put(PROJECTION[1], chapter.name); + cv.put(PROJECTION[2], chapter.number); + cv.put(PROJECTION[3], chapter.url); + cv.put(PROJECTION[4], chapter.provider); + cv.put(PROJECTION[5], chapter.mangaId); + } + + @NonNull + @Override + protected SavedChapter fromCursor(@NonNull Cursor cursor) { + return new SavedChapter( + cursor.getLong(0), + cursor.getString(1), + cursor.getInt(2), + cursor.getString(3), + cursor.getString(4), + cursor.getLong(5) + ); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull SavedChapter chapter) { + return chapter.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + public int count(MangaHeader manga) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase() + .rawQuery("SELECT COUNT(*) FROM " + TABLE_NAME + " WHERE manga_id = ?", + new String[]{String.valueOf(manga.id)}); + return cursor.moveToFirst() ? cursor.getInt(0) : -1; + } catch (Exception e) { + e.printStackTrace(); + return -1; + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + @Nullable + public SavedChapter findChapterByUrl(String chapterUrl) { + Cursor cursor = null; + try { + cursor = mStorageHelper.getReadableDatabase().query( + getTableName(), + getProjection(), + "url = ?", + new String[]{String.valueOf(chapterUrl)}, + null, + null, + null + ); + if (cursor.moveToFirst()) { + return fromCursor(cursor); + } + return null; + } catch (Exception e) { + return null; + } finally { + if (cursor != null) cursor.close(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedChaptersSpecification.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedChaptersSpecification.java new file mode 100644 index 00000000..2875fef5 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedChaptersSpecification.java @@ -0,0 +1,92 @@ +package org.nv95.openmanga.core.storage.db; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaHeader; + +/** + * Created by koitharu on 26.01.18. + */ + +public final class SavedChaptersSpecification implements SqlSpecification { + + @Nullable + private String mOrderBy = null; + @Nullable + private String mLimit = null; + @Nullable + private Long mMangaId = null; + + public SavedChaptersSpecification orderByDate(boolean descending) { + mOrderBy = "created_at"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public SavedChaptersSpecification orderByName(boolean descending) { + mOrderBy = "name"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public SavedChaptersSpecification limit(int limit) { + mLimit = String.valueOf(limit); + return this; + } + + public SavedChaptersSpecification manga(@Nullable MangaHeader manga) { + mMangaId = manga == null ? null : manga.id; + return this; + } + + @Nullable + @Override + public String getSelection() { + return mMangaId == null ? null : "manga_id = ?"; + } + + @Nullable + @Override + public String[] getSelectionArgs() { + return mMangaId == null ? null : new String[]{String.valueOf(mMangaId)}; + } + + @Nullable + @Override + public String getOrderBy() { + return mOrderBy; + } + + @Nullable + @Override + public String getLimit() { + return mLimit; + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(3); + bundle.putString("limit", mLimit); + if (mMangaId != null) { + bundle.putLong("manga_id", mMangaId); + } + bundle.putString("order_by", mOrderBy); + return bundle; + } + + public static SavedChaptersSpecification from(Bundle bundle) { + final SavedChaptersSpecification specification = new SavedChaptersSpecification(); + specification.mLimit = bundle.getString("limit", null); + if (bundle.containsKey("manga_id")) { + specification.mMangaId = bundle.getLong("manga_id"); + } + specification.mOrderBy = bundle.getString("order_by", null); + return specification; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedMangaRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedMangaRepository.java new file mode 100644 index 00000000..9e93b3a4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedMangaRepository.java @@ -0,0 +1,121 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.SavedManga; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 23.01.18. + */ + +public final class SavedMangaRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "saved"; + private static final String[] PROJECTION = new String[]{ + "id", //0 + "name", //1 + "summary", //2 + "genres", //3 + "url", //4 + "thumbnail", //5 + "provider", //6 + "status", //7 + "rating", //8 + "created_at", //9 + "local_path", //10 + "description", //11 + "author" //12 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + private SavedMangaRepository(Context context) { + super(context); + } + + @NonNull + public static synchronized SavedMangaRepository get(Context context) { + SavedMangaRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new SavedMangaRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + @Override + protected void toContentValues(@NonNull SavedManga manga, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], manga.id); + cv.put(PROJECTION[1], manga.name); + cv.put(PROJECTION[2], manga.summary); + cv.put(PROJECTION[3], manga.genres); + cv.put(PROJECTION[4], manga.url); + cv.put(PROJECTION[5], manga.thumbnail); + cv.put(PROJECTION[6], manga.provider); + cv.put(PROJECTION[7], manga.status); + cv.put(PROJECTION[8], manga.rating); + cv.put(PROJECTION[9], manga.createdAt); + cv.put(PROJECTION[10], manga.localPath); + cv.put(PROJECTION[11], manga.description); + cv.put(PROJECTION[12], manga.author); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull SavedManga manga) { + return manga.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + @NonNull + @Override + protected SavedManga fromCursor(@NonNull Cursor cursor) { + return new SavedManga( + cursor.getLong(0), + cursor.getString(1), + cursor.getString(2), + cursor.getString(3), + cursor.getString(4), + cursor.getString(5), + cursor.getString(6), + cursor.getInt(7), + cursor.getShort(8), + cursor.getLong(9), + cursor.getString(10), + cursor.getString(11), + cursor.getString(12) + ); + } + + @Nullable + public SavedManga find(MangaHeader manga) { + return findById(manga.id); + } + + @Nullable + public SavedManga get(long id) { + return findById(id); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedMangaSpecification.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedMangaSpecification.java new file mode 100644 index 00000000..aefc7bec --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedMangaSpecification.java @@ -0,0 +1,77 @@ +package org.nv95.openmanga.core.storage.db; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 26.01.18. + */ + +public final class SavedMangaSpecification implements SqlSpecification { + + @Nullable + private String mOrderBy = null; + @Nullable + private String mLimit = null; + + public SavedMangaSpecification orderByDate(boolean descending) { + mOrderBy = "created_at"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public SavedMangaSpecification orderByName(boolean descending) { + mOrderBy = "name"; + if (descending) { + mOrderBy += " DESC"; + } + return this; + } + + public SavedMangaSpecification limit(int limit) { + mLimit = String.valueOf(limit); + return this; + } + + @Nullable + @Override + public String getSelection() { + return null; + } + + @Nullable + @Override + public String[] getSelectionArgs() { + return null; + } + + @Nullable + @Override + public String getOrderBy() { + return mOrderBy; + } + + @Nullable + @Override + public String getLimit() { + return mLimit; + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(2); + bundle.putString("limit", mLimit); + bundle.putString("order_by", mOrderBy); + return bundle; + } + + public static SavedMangaSpecification from(Bundle bundle) { + final SavedMangaSpecification specification = new SavedMangaSpecification(); + specification.mLimit = bundle.getString("limit", null); + specification.mOrderBy = bundle.getString("order_by", null); + return specification; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedPagesRepository.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedPagesRepository.java new file mode 100644 index 00000000..3d43c352 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedPagesRepository.java @@ -0,0 +1,88 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.SavedPage; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 23.01.18. + */ + +public final class SavedPagesRepository extends SQLiteRepository { + + private static final String TABLE_NAME = "saved_pages"; + private static final String[] PROJECTION = new String[]{ + "id", //0 + "url", //1 + "provider", //2 + "chapter_id", //3 + "number" //4 + }; + + @Nullable + private static WeakReference sInstanceRef = null; + + @NonNull + public static synchronized SavedPagesRepository get(Context context) { + SavedPagesRepository instance = null; + if (sInstanceRef != null) { + instance = sInstanceRef.get(); + } + if (instance == null) { + instance = new SavedPagesRepository(context); + sInstanceRef = new WeakReference<>(instance); + } + return instance; + } + + private SavedPagesRepository(Context context) { + super(context); + } + + @Override + protected void toContentValues(@NonNull SavedPage page, @NonNull ContentValues cv) { + cv.put(PROJECTION[0], page.id); + cv.put(PROJECTION[1], page.url); + cv.put(PROJECTION[2], page.provider); + cv.put(PROJECTION[3], page.chapterId); + cv.put(PROJECTION[4], page.number); + } + + @NonNull + @Override + protected String getTableName() { + return TABLE_NAME; + } + + @NonNull + @Override + protected Object getId(@NonNull SavedPage page) { + return page.id; + } + + @NonNull + @Override + protected String[] getProjection() { + return PROJECTION; + } + + @NonNull + @Override + protected SavedPage fromCursor(@NonNull Cursor cursor) { + return new SavedPage( + cursor.getLong(0), + cursor.getString(1), + cursor.getString(2), + cursor.getLong(3), + cursor.getInt(4) + ); + } + + +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedPagesSpecification.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedPagesSpecification.java new file mode 100644 index 00000000..7d2e18a6 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SavedPagesSpecification.java @@ -0,0 +1,44 @@ +package org.nv95.openmanga.core.storage.db; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaChapter; + +/** + * Created by koitharu on 31.01.18. + */ + +public final class SavedPagesSpecification implements SqlSpecification { + + @Nullable + private final Long mChapterId; + + public SavedPagesSpecification(@Nullable MangaChapter chapter) { + mChapterId = chapter == null ? null : chapter.id; + } + + @Nullable + @Override + public String getSelection() { + return mChapterId == null ? null : "chapter_id = ?"; + } + + @Nullable + @Override + public String[] getSelectionArgs() { + return mChapterId == null ? null : new String[]{String.valueOf(mChapterId)}; + } + + @NonNull + @Override + public String getOrderBy() { + return "number"; + } + + @Nullable + @Override + public String getLimit() { + return null; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/SqlSpecification.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/SqlSpecification.java new file mode 100644 index 00000000..fc0b68fd --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/SqlSpecification.java @@ -0,0 +1,19 @@ +package org.nv95.openmanga.core.storage.db; + +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 24.12.17. + */ + +interface SqlSpecification { + + @Nullable + String getSelection(); + @Nullable + String[] getSelectionArgs(); + @Nullable + String getOrderBy(); + @Nullable + String getLimit(); +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/db/StorageHelper.java b/app/src/main/java/org/nv95/openmanga/core/storage/db/StorageHelper.java new file mode 100644 index 00000000..e694ae7e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/db/StorageHelper.java @@ -0,0 +1,54 @@ +package org.nv95.openmanga.core.storage.db; + +import android.content.Context; +import android.content.res.Resources; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.utils.TextUtils; + +/** + * Created by koitharu on 24.12.17. + */ + +public class StorageHelper extends SQLiteOpenHelper { + + private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "storage"; + + private final Resources mResources; + + public StorageHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + mResources = context.getResources(); + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + String[] parts = ResourceUtils.getRawString(mResources, R.raw.storage).split(";"); + sqLiteDatabase.beginTransaction(); + try { + for (String query : parts) { + sqLiteDatabase.execSQL(TextUtils.inline(query)); + } + sqLiteDatabase.setTransactionSuccessful(); + /*} catch (Exception e) { //TODO handle it + e.printStackTrace();*/ + } finally { + sqLiteDatabase.endTransaction(); + } + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { + + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/files/FilesStorage.java b/app/src/main/java/org/nv95/openmanga/core/storage/files/FilesStorage.java new file mode 100644 index 00000000..bbcb7f64 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/files/FilesStorage.java @@ -0,0 +1,30 @@ +package org.nv95.openmanga.core.storage.files; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; + +import java.io.File; + +/** + * Created by koitharu on 22.01.18. + */ + +public interface FilesStorage { + + @NonNull + File getFile(@NonNull K key); + + @Nullable + V get(@NonNull K key); + + void put(@NonNull K key, @Nullable V v); + + boolean remove(@NonNull K key); + + @WorkerThread + void clear(); + + @WorkerThread + long size(); +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/files/SavedPagesStorage.java b/app/src/main/java/org/nv95/openmanga/core/storage/files/SavedPagesStorage.java new file mode 100644 index 00000000..7ee1a31a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/files/SavedPagesStorage.java @@ -0,0 +1,79 @@ +package org.nv95.openmanga.core.storage.files; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.utils.FilesystemUtils; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.SavedManga; +import org.nv95.openmanga.core.models.SavedPage; +import org.nv95.openmanga.core.storage.db.SavedMangaRepository; + +import java.io.File; + +/** + * Created by koitharu on 25.01.18. + */ + +public final class SavedPagesStorage implements FilesStorage { + + private final File mRootDirectory; + + public SavedPagesStorage(@NonNull SavedManga manga) { + mRootDirectory = new File(manga.localPath); + //noinspection ResultOfMethodCallIgnored + mRootDirectory.mkdirs(); + } + + @NonNull + @Override + public File getFile(@NonNull SavedPage key) { + return new File(mRootDirectory, encodeName(key)); + } + + @Nullable + @Override + public File get(@NonNull SavedPage key) { + final File file = getFile(key); + return file.exists() ? file : null; + } + + @Override + public void put(@NonNull SavedPage key, @Nullable File file) { + final File f = getFile(key); + if (f.exists()) { + f.delete(); + } + if (file != null) { + //TODO not implemented + } + } + + @Override + public boolean remove(@NonNull SavedPage key) { + File file = getFile(key); + return file.exists() && file.delete(); + } + + @Override + public void clear() { + FilesystemUtils.clearDir(mRootDirectory); + } + + @Override + public long size() { + return FilesystemUtils.getFileSize(mRootDirectory); + } + + @NonNull + private static String encodeName(SavedPage page) { + return page.chapterId + "_" + page.id; + } + + @Nullable + public static SavedPagesStorage get(@NonNull Context context, @NonNull MangaHeader manga) { + final SavedManga savedManga = SavedMangaRepository.get(context).find(manga); + return savedManga == null ? null : new SavedPagesStorage(savedManga); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/files/ThumbnailsStorage.java b/app/src/main/java/org/nv95/openmanga/core/storage/files/ThumbnailsStorage.java new file mode 100644 index 00000000..5f7ea035 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/files/ThumbnailsStorage.java @@ -0,0 +1,90 @@ +package org.nv95.openmanga.core.storage.files; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.utils.FilesystemUtils; +import org.nv95.openmanga.core.models.MangaBookmark; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class ThumbnailsStorage implements FilesStorage { + + private final File mRootDirectory; + + public ThumbnailsStorage(Context context) { + mRootDirectory = new File(context.getExternalFilesDir("thumb"), "bookmarks"); + mRootDirectory.mkdirs(); + } + + @NonNull + @Override + public File getFile(@NonNull MangaBookmark key) { + return new File(mRootDirectory, encodeName(key)); + } + + @Nullable + @Override + public Bitmap get(@NonNull MangaBookmark key) { + final File f = getFile(key); + if (!f.exists()) { + return null; + } + return BitmapFactory.decodeFile(f.getPath()); + } + + @Override + public void put(@NonNull MangaBookmark key, @Nullable Bitmap bitmap) { + final File f = getFile(key); + if (f.exists()) { + f.delete(); + } + if (bitmap != null) { + FileOutputStream output = null; + try { + output = new FileOutputStream(f); + bitmap.compress(Bitmap.CompressFormat.PNG, 90, output); + output.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (output != null) { + output.close(); + } + } catch (IOException ignored) { + } + } + } + } + + @Override + public boolean remove(@NonNull MangaBookmark key) { + File file = getFile(key); + return file.exists() && file.delete(); + } + + @Override + public void clear() { + FilesystemUtils.clearDir(mRootDirectory); + } + + @Override + public long size() { + return FilesystemUtils.getFileSize(mRootDirectory); + } + + @NonNull + private static String encodeName(MangaBookmark bookmark) { + return String.valueOf(bookmark.id); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/settings/AppSettings.java b/app/src/main/java/org/nv95/openmanga/core/storage/settings/AppSettings.java new file mode 100644 index 00000000..10615e45 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/settings/AppSettings.java @@ -0,0 +1,72 @@ +package org.nv95.openmanga.core.storage.settings; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 26.12.17. + */ + +public class AppSettings { + + @Nullable + private static WeakReference sInstanceReference = null; + + private final SharedPreferences mPreferences; + public final ReaderSettings readerSettings; + public final ShelfSettings shelfSettings; + + private AppSettings(Context context) { + mPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + readerSettings = new ReaderSettings(mPreferences); + shelfSettings = new ShelfSettings(mPreferences); + } + + public static AppSettings get(Context context) { + AppSettings instance = sInstanceReference == null ? null : sInstanceReference.get(); + if (instance == null) { + instance = new AppSettings(context); + sInstanceReference = new WeakReference<>(instance); + } + return instance; + } + + public static NetworkSettings getNetworkSettings(Context context) { + NetworkSettings instance = NetworkSettings.sInstanceReference == null ? null : NetworkSettings.sInstanceReference.get(); + if (instance == null) { + instance = new NetworkSettings(context); + NetworkSettings.sInstanceReference = new WeakReference<>(instance); + } + return instance; + } + + public boolean isUseTor() { + return mPreferences.getBoolean("use_tor", false); + } + + public String getAppLocale() { + return mPreferences.getString("lang", ""); + } + + public int getCacheMaxSizeMb() { + int value = mPreferences.getInt("cache_max", 100); + if (value < 20) { + value = 20; //20M + } else if (value > 1024) { + value = 1024; //1G + } + return value; + } + + public int getAppTheme() { + try { + return Integer.parseInt(mPreferences.getString("theme", "0")); + } catch (Exception e) { + return 0; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/settings/NetworkSettings.java b/app/src/main/java/org/nv95/openmanga/core/storage/settings/NetworkSettings.java new file mode 100644 index 00000000..dcad135f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/settings/NetworkSettings.java @@ -0,0 +1,43 @@ +package org.nv95.openmanga.core.storage.settings; + +import android.content.Context; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.os.Build; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; + +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 17.01.18. + */ + +final class NetworkSettings { + + @Nullable + static WeakReference sInstanceReference = null; + + private final SharedPreferences mPreferences; + private final ConnectivityManager mConnectivityManager; + + NetworkSettings(Context context) { + mPreferences = PreferenceManager.getDefaultSharedPreferences(context); + mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + public boolean isNetworkSaveMode() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && mConnectivityManager.isActiveNetworkMetered() + && mConnectivityManager.getRestrictBackgroundStatus() + == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; + } + + private boolean isUseSystem() { + return mPreferences.getBoolean("network.usage.system", true); + } + + public boolean isThumbnailsWifiOnly() { + return "1".equals(mPreferences.getString("network.usage.show_thumbnails", "0")); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/settings/ReaderSettings.java b/app/src/main/java/org/nv95/openmanga/core/storage/settings/ReaderSettings.java new file mode 100644 index 00000000..0dcaa502 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/settings/ReaderSettings.java @@ -0,0 +1,55 @@ +package org.nv95.openmanga.core.storage.settings; + +import android.content.SharedPreferences; +import android.graphics.Color; +import android.support.annotation.ColorInt; + +/** + * Created by koitharu on 06.02.18. + */ + +public final class ReaderSettings { + + private final SharedPreferences mPreferences; + + ReaderSettings(SharedPreferences preferences) { + mPreferences = preferences; + } + + public short getDefaultPreset() { + try { + return Short.parseShort(mPreferences.getString("reader.default_preset", "0")); + } catch (Exception e) { + return 0; + } + } + + public boolean isVolumeKeysEnabled() { + return mPreferences.getBoolean("reader.volume_keys", true); + } + + public boolean isWakelockEnabled() { + return mPreferences.getBoolean("reader.wakelock", true); + } + + public boolean isBrightnessAdjustEnabled() { + return mPreferences.getBoolean("reader.brightness_adjust", false); + } + + public int getBrightnessValue() { + return mPreferences.getInt("reader.brightness_value", 20); + } + + public boolean isStatusBarEnbaled() { + return mPreferences.getBoolean("reader.statusbar", true); + } + + public boolean isCustomBackground() { + return mPreferences.getBoolean("reader.background_apply", false); + } + + @ColorInt + public int getBackgroundColor() { + return mPreferences.getInt("reader.background", Color.BLACK); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/core/storage/settings/ShelfSettings.java b/app/src/main/java/org/nv95/openmanga/core/storage/settings/ShelfSettings.java new file mode 100644 index 00000000..6d0c7ef8 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/core/storage/settings/ShelfSettings.java @@ -0,0 +1,64 @@ +package org.nv95.openmanga.core.storage.settings; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import org.nv95.openmanga.core.models.Category; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class ShelfSettings { + + private final SharedPreferences mPreferences; + + ShelfSettings(SharedPreferences preferences) { + mPreferences = preferences; + } + + public boolean isRecentEnabled() { + return mPreferences.getBoolean("shelf.recent_enabled", true); + } + + public boolean isHistoryEnabled() { + return mPreferences.getBoolean("shelf.history_enabled", true); + } + + public int getMaxHistoryRows() { + return mPreferences.getInt("shelf.history_rows", 1); + } + + public boolean isFavouritesEnabled() { + return mPreferences.getBoolean("shelf.favourites_enabled", true); + } + + public ArrayList getEnabledCategories(ArrayList allCategories){ + if (allCategories == null) { + return new ArrayList<>(0); + } + final Set enabledCats = mPreferences.getStringSet("shelf.favourites_categories", null); + if (enabledCats == null) { + return allCategories; + } + final ArrayList result = new ArrayList<>(enabledCats.size()); + for (Category o : allCategories) { + if (enabledCats.contains(String.valueOf(o.id))) { + result.add(o); + } + } + return result; + } + + public int getMaxFavouritesRows() { + return mPreferences.getInt("shelf.favourites_cat_rows", 1); + } + + public static void onCategoryAdded(Context context, Category category) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + final Set enabledCats = preferences.getStringSet("shelf.favourites_categories", new HashSet<>(1)); + enabledCats.add(String.valueOf(category.id)); + preferences.edit().putStringSet("shelf.favourites_categories", enabledCats).apply(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/BookmarksDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/BookmarksDialog.java deleted file mode 100644 index 5f07d5f8..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/BookmarksDialog.java +++ /dev/null @@ -1,250 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Canvas; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; -import android.support.v4.util.Pair; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.ReadActivity2; -import org.nv95.openmanga.adapters.BookmarksAdapter; -import org.nv95.openmanga.items.Bookmark; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.BookmarksProvider; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; - -import java.util.ArrayList; - -/** - * Created by nv95 on 20.11.16. - */ - -public class BookmarksDialog implements BookmarksAdapter.OnBookmarkClickListener, View.OnClickListener { - - private final Activity mActivity; - private final AlertDialog mDialog; - private final View mContentView; - private final TextView mHolder; - private final RecyclerView mRecyclerView; - private final Toolbar mToolbar; - - private ArrayList mBookmarks; - - public BookmarksDialog(Activity context) { - mActivity = context; - mContentView = LayoutInflater.from(context) - .inflate(R.layout.dialog_bookmarks, null, false); - mRecyclerView = mContentView.findViewById(R.id.recyclerView); - mToolbar = mContentView.findViewById(R.id.toolbar); - mHolder = mContentView.findViewById(R.id.textView_holder); - mToolbar.setNavigationIcon(R.drawable.ic_cancel_light); - mToolbar.setNavigationOnClickListener(this); - mToolbar.setTitle(R.string.bookmarks); - mRecyclerView.setLayoutManager(new LinearLayoutManager(context)); - - mDialog = new AlertDialog.Builder(context) - .setView(mContentView) - .create(); - mDialog.setOwnerActivity(mActivity); - - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { - - private Drawable background; - private Drawable xMark; - boolean initialized = false; - - private void init() { - background = new ColorDrawable(ContextCompat.getColor(mActivity, R.color.red_overlay)); - xMark = ContextCompat.getDrawable(mActivity, R.drawable.ic_delete_light); - initialized = true; - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - int pos = viewHolder.getAdapterPosition(); - if (BookmarksProvider.getInstance(mActivity).remove(mBookmarks.get(pos).hashCode())) { - mBookmarks.remove(pos); - mRecyclerView.getAdapter().notifyItemRemoved(pos); - showHideHolder(); - Snackbar.make(mContentView, R.string.bookmark_removed, Snackbar.LENGTH_SHORT).show(); - } - } - - @Override - public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, - float dX, float dY, int actionState, boolean isCurrentlyActive) { - - if (viewHolder.getAdapterPosition() == -1) { - return; - } - - View itemView = viewHolder.itemView; - - if (!initialized) { - init(); - } - - background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom()); - background.draw(c); - - int itemHeight = itemView.getBottom() - itemView.getTop(); - int intrinsicWidth = xMark.getIntrinsicWidth(); - int intrinsicHeight = xMark.getIntrinsicWidth(); - - int xMarkPos = (int) ((dX + intrinsicHeight) / 2); - xMark.setAlpha(Math.min(255, (int)(-dX))); - - int xMarkLeft = itemView.getRight() + xMarkPos - intrinsicWidth; - int xMarkRight = itemView.getRight() + xMarkPos; - int xMarkTop = itemView.getTop() + (itemHeight - intrinsicHeight)/2; - int xMarkBottom = xMarkTop + intrinsicHeight; - xMark.setBounds(xMarkLeft, xMarkTop, xMarkRight, xMarkBottom); - - xMark.draw(c); - - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - } - }); - itemTouchHelper.attachToRecyclerView(mRecyclerView); - } - - public void show() { - mBookmarks = BookmarksProvider.getInstance(mActivity).getAll(); - mRecyclerView.setAdapter(new BookmarksAdapter(mBookmarks, this)); - showHideHolder(); - mDialog.show(); - } - - public void show(MangaInfo manga) { - mToolbar.setSubtitle(manga.name); - mBookmarks = BookmarksProvider.getInstance(mActivity).getAll(manga.id); - mRecyclerView.setAdapter(new BookmarksAdapter(mBookmarks, this)); - showHideHolder(); - mDialog.show(); - } - - private void showHideHolder() { - if (mBookmarks.isEmpty()) { - mHolder.setVisibility(View.VISIBLE); - mRecyclerView.setVisibility(View.GONE); - } else { - mHolder.setVisibility(View.GONE); - mRecyclerView.setVisibility(View.VISIBLE); - } - } - - @Override - public void onBookmarkSelected(Bookmark bookmark) { - mDialog.dismiss(); - new BookmarkOpenTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, bookmark); - } - - @Override - public void onClick(View v) { - mDialog.dismiss(); - } - - - private class BookmarkOpenTask extends AsyncTask> implements DialogInterface.OnCancelListener { - - private final ProgressDialog mProgressDialog; - - BookmarkOpenTask() { - mProgressDialog = new ProgressDialog(mActivity); - mProgressDialog.setIndeterminate(true); - mProgressDialog.setCancelable(true); - mProgressDialog.setOnCancelListener(this); - mProgressDialog.setMessage(mActivity.getString(R.string.loading)); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mProgressDialog.show(); - } - - @Override - protected Pair doInBackground(Bookmark... params) { - try { - Intent intent; - HistoryProvider historyProvider = HistoryProvider.getInstance(mActivity); - MangaInfo info = historyProvider.get(params[0].mangaId); - if (info == null) { - return new Pair<>(2, null); - } - MangaProvider provider; - if (info.provider.equals(LocalMangaProvider.class)) { - provider = LocalMangaProvider.getInstance(mActivity); - } else { - if (!MangaProviderManager.checkConnection(mActivity)) { - return new Pair<>(1, null); - } - provider = MangaProviderManager.instanceProvider(mActivity, info.provider); - } - MangaSummary summary = provider.getDetailedInfo(info); - intent = new Intent(mActivity, ReadActivity2.class); - intent.putExtras(summary.toBundle()); - intent.putExtra("chapter", summary.getChapters().indexByNumber(params[0].chapter)); - intent.putExtra("page", params[0].page); - return new Pair<>(0, intent); - } catch (Exception e) { - return new Pair<>(3, null); - } - } - - @Override - protected void onPostExecute(Pair result) { - super.onPostExecute(result); - mProgressDialog.dismiss(); - int msg; - switch (result.first) { - case 0: - mActivity.startActivity(result.second); - return; - case 1: - msg = R.string.no_network_connection; - break; - case 2: - msg = R.string.history_empty; - break; - default: - msg = R.string.error; - break; - } - new AlertDialog.Builder(mActivity) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, null) - .setMessage(mActivity.getString(msg)) - .create().show(); - } - - @Override - public void onCancel(DialogInterface dialog) { - this.cancel(false); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/ChaptersSelectDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/ChaptersSelectDialog.java deleted file mode 100644 index 79441c8c..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/ChaptersSelectDialog.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.content.Context; -import android.content.DialogInterface; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.Toolbar; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.ListView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.MangaSaveHelper; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.LocalMangaProvider; - -import java.util.ArrayList; - -/** - * Created by nv95 on 27.09.16. - */ - -public class ChaptersSelectDialog implements Toolbar.OnMenuItemClickListener, View.OnClickListener { - - private final AlertDialog mDialog; - private final View mContentView; - - private final ListView mListView; - private final Toolbar mToolbar; - - public ChaptersSelectDialog(Context context) { - mContentView = LayoutInflater.from(context) - .inflate(R.layout.dialog_chapters, null, false); - mListView = mContentView.findViewById(R.id.listView); - mToolbar = mContentView.findViewById(R.id.toolbar); - mToolbar.setNavigationIcon(R.drawable.ic_cancel_light); - mToolbar.inflateMenu(R.menu.chapters); - mToolbar.setNavigationOnClickListener(this); - mToolbar.setOnMenuItemClickListener(this); - - mDialog = new AlertDialog.Builder(context) - .setView(mContentView) - .setNegativeButton(android.R.string.cancel, null) - .create(); - } - - public void showSave(MangaSummary mangaSummary, @StringRes int title) { - mToolbar.setTitle(title); - mToolbar.setSubtitle(mContentView.getContext().getString(R.string.chapters_total, mangaSummary.chapters.size())); - mListView.setAdapter(new ArrayAdapter<>( - mContentView.getContext(), - R.layout.item_multiple_choice, - mangaSummary.getChapters().getNames() - )); - checkUnsaved(mangaSummary); - mDialog.setButton(DialogInterface.BUTTON_POSITIVE, mContentView.getContext().getString(R.string.action_save), new SaveClickListener(mangaSummary)); - mDialog.show(); - } - - public void showRemove(MangaSummary mangaSummary, OnChaptersRemoveListener listener) { - mToolbar.setTitle(R.string.action_remove); - mToolbar.setSubtitle(mContentView.getContext().getString(R.string.chapters_total, mangaSummary.chapters.size())); - mListView.setAdapter(new ArrayAdapter<>( - mContentView.getContext(), - R.layout.item_multiple_choice, - mangaSummary.getChapters().getNames() - )); - checkAll(); - mDialog.setButton(DialogInterface.BUTTON_POSITIVE, mContentView.getContext().getString(R.string.action_remove), new RemoveClickListener(mangaSummary, listener)); - mDialog.show(); - } - - private void checkAll() { - for (int i=mListView.getCount() - 1;i>=0;i--) { - mListView.setItemChecked(i, true); - } - } - - private void checkUnsaved(MangaSummary mangaSummary) { - ArrayList ids = LocalMangaProvider.getInstance(mDialog.getContext()) - .getLocalChaptersNumbers(mangaSummary.id); - for (int i=mListView.getCount() - 1;i>=0;i--) { - mListView.setItemChecked(i, !ids.contains(mangaSummary.chapters.get(i).number)); - } - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_checkall: - for (int i=mListView.getCount() - 1;i>=0;i--) { - mListView.setItemChecked(i, !mListView.isItemChecked(i)); - } - break; - - } - return true; - } - - @Override - public void onClick(View v) { - mDialog.dismiss(); - } - - private class SaveClickListener implements DialogInterface.OnClickListener { - - private final MangaSummary mMangaSummary; - - private SaveClickListener(MangaSummary manga) { - mMangaSummary = manga; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - MangaSummary copy = new MangaSummary(mMangaSummary); - copy.chapters.clear(); - for (int i=0;i parent, View view, int position, long id) { - if (position == 0) { - File dir = mAdapter.getCurrentDir().getParentFile(); - if (dir != null) { - mAdapter.setCurrentDir(mAdapter.getCurrentDir().getParentFile()); - } - } else { - mAdapter.setCurrentDir(mAdapter.getItem(position - 1)); - } - mHeaderUp.setText(mAdapter.getCurrentDir().getPath()); - mAdapter.notifyDataSetChanged(); - } - - public DirSelectDialog setDirSelectListener(OnDirSelectListener dirSelectListener) { - this.mDirSelectListener = dirSelectListener; - return this; - } - - public void show() { - mDialog.show(); - } - - public interface OnDirSelectListener { - void onDirSelected(File dir); - } - -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/FastHistoryDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/FastHistoryDialog.java deleted file mode 100644 index 59dac0c2..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/FastHistoryDialog.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.graphics.drawable.ColorDrawable; -import android.os.AsyncTask; -import android.os.Build; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.view.animation.AnimationUtils; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.FastHistoryAdapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.utils.QuickReadTask; -import org.nv95.openmanga.utils.choicecontrol.OnHolderClickListener; - -/** - * Created by admin on 19.07.17. - */ - -public class FastHistoryDialog implements OnHolderClickListener { - - private final Dialog mDialog; - private final RecyclerView mRecyclerView; - private final HistoryProvider mProvider; - private final TextView mTextViewHolder; - - public FastHistoryDialog(Context context) { - View view = LayoutInflater.from(context).inflate(R.layout.dialog_fasthist, null, false); - mRecyclerView = view.findViewById(R.id.recyclerView); - LinearLayoutManager layoutManager = new LinearLayoutManager(context); - layoutManager.setStackFromEnd(true); - layoutManager.setReverseLayout(true); - mRecyclerView.setLayoutManager(layoutManager); - mTextViewHolder = view.findViewById(R.id.textView_holder); - mDialog = new Dialog(context, R.style.FullScreenDialog); - if (context instanceof Activity) { - mDialog.setOwnerActivity((Activity) context); - } - mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - mDialog.setContentView(view); - final int color = ContextCompat.getColor(context, R.color.transparent_dark); - final Window window = mDialog.getWindow(); - assert window != null; - window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - window.setBackgroundDrawable(new ColorDrawable(color)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - view.setFitsSystemWindows(true); - } - mProvider = HistoryProvider.getInstance(context); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mRecyclerView.startAnimation(AnimationUtils.loadAnimation(mDialog.getContext(), R.anim.up_to_bottom)); - mDialog.dismiss(); - } - }); - } - - public void show(int maxItems) { - MangaList list = mProvider.getLast(maxItems); - if (list.isEmpty()) { - mTextViewHolder.setVisibility(View.VISIBLE); - } else { - mRecyclerView.setAdapter(new FastHistoryAdapter(list, this)); - mRecyclerView.startAnimation(AnimationUtils.loadAnimation(mDialog.getContext(), R.anim.up_from_bottom)); - } - mDialog.show(); - } - - @Override - public boolean onClick(RecyclerView.ViewHolder viewHolder) { - FastHistoryAdapter adapter = (FastHistoryAdapter) mRecyclerView.getAdapter(); - MangaInfo mangaInfo = adapter.getItem(viewHolder.getAdapterPosition()); - new QuickReadTask(mDialog.getContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mangaInfo); - mDialog.dismiss(); - return true; - } - - @Override - public boolean onLongClick(RecyclerView.ViewHolder viewHolder) { - return false; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/HintDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/HintDialog.java deleted file mode 100644 index 42cbbd27..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/HintDialog.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.StringRes; - -/** - * Created by nv95 on 05.12.16. - */ - -public class HintDialog { - - private final Dialog mDialog; - - private HintDialog(Context context, @StringRes int text) { - mDialog = new AlertDialog.Builder(context) - .setMessage(text) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, null) - .create(); - } - - private void show() { - mDialog.show(); - } - - public static boolean showOnce(Context context, @StringRes int hint) { - SharedPreferences prefs = context.getSharedPreferences("tips", Context.MODE_PRIVATE); - final String key = "tip_" + hint; - if (prefs.getBoolean(key, false)) { - return false; - } - new HintDialog(context, hint).show(); - prefs.edit() - .putBoolean(key, true) - .apply(); - return true; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/LocalMoveDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/LocalMoveDialog.java deleted file mode 100644 index 0663e5f2..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/LocalMoveDialog.java +++ /dev/null @@ -1,254 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.annotation.SuppressLint; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.os.PowerManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.text.format.Formatter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.LocalMangaInfo; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.utils.MangaStore; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Created by nv95 on 02.07.16. - */ - -public class LocalMoveDialog { - - private final Context mContext; - private final long[] mIds; - @Nullable - private String mDestinaton = null; - - public LocalMoveDialog(Context context, long... ids) { - mContext = context; - mIds = ids; - } - - public LocalMoveDialog setDestination(String path) { - mDestinaton = path; - return this; - } - - public void showSelectSource(@Nullable String exclude) { - new LoadDataTask(exclude).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void showSelectDestination(final LocalMangaInfo[] mangas) { - new DirSelectDialog(mContext) - .setDirSelectListener(new DirSelectDialog.OnDirSelectListener() { - @Override - public void onDirSelected(File dir) { - mDestinaton = dir.getPath(); - showMove(mangas); - } - }).show(); - } - - public void showMove(LocalMangaInfo[] mangas) { - new MoveMangaTask(mDestinaton).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mangas); - } - - private class MoveMangaTask extends AsyncTask { - - private final ProgressDialog mProgressDialog; - private PowerManager.WakeLock mWakeLock; - private final String mDest; - - MoveMangaTask(String dest) { - mDest = dest; - mWakeLock = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Moving manga"); - mProgressDialog = new ProgressDialog(mContext); - mProgressDialog.setTitle(R.string.moving_files); - mProgressDialog.setMessage(mContext.getString(R.string.loading)); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - mProgressDialog.setCancelable(false); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mProgressDialog.show(); - mWakeLock.acquire(); - } - - @Override - protected Long doInBackground(LocalMangaInfo... params) { - MangaStore store = new MangaStore(mContext); - long totalSize = 0; - for (int i = 0; i < params.length; i++) { - publishProgress(i, params.length, params[i].name); - if (params[i].path.equals(mDest)) { - continue; - } - if (!store.moveManga(params[i].id, mDest)) { - return null; - } - totalSize += params[i].size; - } - return totalSize; - } - - @Override - protected void onProgressUpdate(Object... values) { - super.onProgressUpdate(values); - mProgressDialog.setMax((Integer) values[1]); - mProgressDialog.setProgress((Integer) values[0]); - mProgressDialog.setMessage((CharSequence) values[2]); - } - - @Override - protected void onPostExecute(Long aLong) { - super.onPostExecute(aLong); - mProgressDialog.dismiss(); - mWakeLock.release(); - String msg = aLong == null ? - mContext.getString(R.string.error) : mContext.getString( - R.string.mangas_moved_done, mDest, - Formatter.formatFileSize(mContext, aLong) - ); - new AlertDialog.Builder(mContext) - .setMessage(msg) - .setPositiveButton(android.R.string.ok, null) - .setCancelable(true) - .create().show(); - } - } - - private class LoadDataTask extends AsyncTask> { - - private final ProgressDialog mProgressDialog; - private final String mExclude; - - LoadDataTask(String exclude) { - mExclude = exclude; - mProgressDialog = new ProgressDialog(mContext); - mProgressDialog.setMessage(mContext.getString(R.string.loading)); - mProgressDialog.setIndeterminate(true); - mProgressDialog.setCancelable(false); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mProgressDialog.show(); - } - - @Override - protected List doInBackground(Void... params) { - LocalMangaInfo[] infos = LocalMangaProvider.getInstance(mContext) - .getLocalInfo(mIds); - ArrayList res = new ArrayList<>(); - for (LocalMangaInfo o : infos) { - if (o != null && !o.path.equals(mExclude)) { - res.add(o); - } - } - return res; - } - - @Override - protected void onPostExecute(List localMangaInfos) { - super.onPostExecute(localMangaInfos); - mProgressDialog.dismiss(); - if (localMangaInfos.size() == 0) { - Toast.makeText(mContext, R.string.no_manga_found, Toast.LENGTH_SHORT).show(); - return; - } - final SelectAdapter adapter = new SelectAdapter(mContext, localMangaInfos); - ListView listView = new ListView(mContext); - listView.setAdapter(adapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - adapter.toggle(position); - } - }); - new AlertDialog.Builder(mContext) - .setView(listView) - .setTitle(R.string.move_saved) - .setPositiveButton(R.string.next, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - LocalMangaInfo[] items = adapter.getCheckedItems(); - if (items.length == 0) { - Toast.makeText(mContext, R.string.nothing_selected, Toast.LENGTH_SHORT).show(); - } else { - if (mDestinaton == null) { - showSelectDestination(items); - } else { - showMove(items); - } - } - } - }) - .setNegativeButton(android.R.string.cancel, null) - .setCancelable(true) - .create().show(); - } - } - - private static class SelectAdapter extends ArrayAdapter { - - final boolean[] mChecked; - - SelectAdapter(Context context, List objects) { - super(context, R.layout.item_adapter_checkable, objects); - mChecked = new boolean[objects.size()]; - Arrays.fill(mChecked, true); - } - - @NonNull - @SuppressLint("SetTextI18n") - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View v = convertView != null ? convertView : LayoutInflater.from(getContext()) - .inflate(R.layout.item_adapter_checkable, parent, false); - LocalMangaInfo item = getItem(position); - ((TextView) v.findViewById(android.R.id.text1)).setText(item.name); - ((TextView) v.findViewById(android.R.id.text2)).setText( - new File(item.path).getParent() - + "\n" - + Formatter.formatFileSize(getContext(), item.size) - ); - ((CheckBox) v.findViewById(android.R.id.checkbox)).setChecked(mChecked[position]); - return v; - } - - public LocalMangaInfo[] getCheckedItems() { - ArrayList list = new ArrayList<>(); - for (int i = 0; i < getCount(); i++) { - if (mChecked[i]) { - list.add(getItem(i)); - } - } - return list.toArray(new LocalMangaInfo[list.size()]); - } - - public void toggle(int position) { - mChecked[position] = !mChecked[position]; - notifyDataSetChanged(); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/MenuDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/MenuDialog.java deleted file mode 100644 index 5f175857..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/MenuDialog.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.DialogInterface; -import android.support.annotation.MenuRes; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.support.v7.view.menu.MenuAdapter; -import android.support.v7.view.menu.MenuBuilder; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.MenuItem; - -/** - * Created by admin on 24.07.17. - */ - -@SuppressLint("RestrictedApi") -public class MenuDialog implements DialogInterface.OnClickListener { - - private final AlertDialog mDialog; - private final MenuBuilder mMenu; - @Nullable - private MenuItem.OnMenuItemClickListener mItemClickListener; - - public MenuDialog(Context context, @MenuRes int menuId, @Nullable CharSequence title) { - mMenu = new MenuBuilder(context); - new MenuInflater(context).inflate(menuId, mMenu); - mDialog = new AlertDialog.Builder(context) - .setAdapter(new MenuAdapter(mMenu, LayoutInflater.from(context), false), this) - .setTitle(title) - .create(); - } - - public MenuDialog setOnItemClickListener(MenuItem.OnMenuItemClickListener listener) { - mItemClickListener = listener; - return this; - } - - public void show() { - mDialog.show(); - } - - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (mItemClickListener != null) { - mItemClickListener.onMenuItemClick(mMenu.getItem(i)); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/NavigationListener.java b/app/src/main/java/org/nv95/openmanga/dialogs/NavigationListener.java deleted file mode 100644 index df215cdc..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/NavigationListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.nv95.openmanga.dialogs; - -/** - * Created by nv95 on 09.10.16. - */ - -public interface NavigationListener { - void onPageChange(int page); -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/PageNumberDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/PageNumberDialog.java deleted file mode 100644 index 5a6ae301..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/PageNumberDialog.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.content.Context; -import android.content.DialogInterface; -import android.support.v7.app.AlertDialog; -import android.view.View; -import android.widget.EditText; -import android.widget.SeekBar; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * Created by nv95 on 09.10.16. - */ - -public class PageNumberDialog implements DialogInterface.OnClickListener, SeekBar.OnSeekBarChangeListener, DialogInterface.OnShowListener { - - private AlertDialog mDialog; - private NavigationListener mNavigationListener; - private Context mContext; - //controls - private EditText mEditText; - private TextView mTextView; - - public PageNumberDialog(Context context) { - this.mContext = context; - View view = View.inflate(context, R.layout.dialog_pagenumber, null); - mEditText = view.findViewById(R.id.editText); - mTextView = view.findViewById(R.id.textView); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setView(view); - builder.setTitle(R.string.navigate); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - builder.setPositiveButton(R.string.navigate, this); - mDialog = builder.create(); - mDialog.setOnShowListener(this); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - mTextView.setText(mContext.getString(R.string.goto_page, progress + 1, seekBar.getMax())); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - - } - - public void show(int current) { - mEditText.setText(String.valueOf(current)); - mDialog.show(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (mNavigationListener != null) - try { - int num = Integer.parseInt(mEditText.getText().toString()); - if (num < 0) { - Toast.makeText(mContext, R.string.invalid_value, Toast.LENGTH_SHORT).show(); - } else { - mNavigationListener.onPageChange(num); - } - } catch (NumberFormatException e) { - Toast.makeText(mContext, R.string.invalid_value, Toast.LENGTH_SHORT).show(); - } - } - - public PageNumberDialog setNavigationListener(NavigationListener navigationListener) { - this.mNavigationListener = navigationListener; - return this; - } - - @Override - public void onShow(DialogInterface dialog) { - mEditText.setSelection(mEditText.getText().length()); - LayoutUtils.showSoftKeyboard(mEditText); - } -} - diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/PreviewDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/PreviewDialog.java deleted file mode 100644 index 08543f10..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/PreviewDialog.java +++ /dev/null @@ -1,151 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.ReadActivity2; -import org.nv95.openmanga.adapters.ChaptersAdapter; -import org.nv95.openmanga.adapters.OnChapterClickListener; -import org.nv95.openmanga.items.Bookmark; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.utils.AnimUtils; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.lang.ref.WeakReference; -import java.util.List; - -/** - * Created by admin on 16.08.17. - */ - -public class PreviewDialog implements DialogInterface.OnDismissListener, OnChapterClickListener { - - private final AlertDialog mDialog; - private final View mContentView; - private final TextView mHolder; - private final ProgressBar mProgressBar; - private final RecyclerView mRecyclerView; - private final ChaptersAdapter mChaptersAdapter; - private MangaSummary mManga; - private WeakReference mTaskRef; - - public PreviewDialog(Context context) { - mContentView = LayoutInflater.from(context) - .inflate(R.layout.dialog_preview, null, false); - mRecyclerView = mContentView.findViewById(R.id.recyclerView); - mHolder = mContentView.findViewById(R.id.textView_holder); - mProgressBar = mContentView.findViewById(R.id.progressBar); - mChaptersAdapter = new ChaptersAdapter(context); - mChaptersAdapter.setOnItemClickListener(this); - mRecyclerView.setAdapter(mChaptersAdapter); - - mDialog = new AlertDialog.Builder(context) - .setView(mContentView) - .setCancelable(true) - .setOnDismissListener(this) - .create(); - } - - public void show(MangaInfo mangaInfo) { - mManga = new MangaSummary(mangaInfo); - LoadTask task = new LoadTask(this); - mTaskRef = new WeakReference<>(task); - task.start(); - mDialog.show(); - } - - @Override - public void onDismiss(DialogInterface dialogInterface) { - WeakAsyncTask.cancel(mTaskRef, true); - } - - @Override - public void onChapterClick(int pos, MangaChapter chapter) { - if (pos == -1) { - Intent intent = new Intent(mDialog.getContext(), ReadActivity2.class); - intent.putExtras(mManga.toBundle()); - HistoryProvider.HistorySummary hs = HistoryProvider.getInstance(mDialog.getContext()) - .get(mManga); - if (hs != null) { - int index = mManga.chapters.indexByNumber(hs.getChapter()); - if (index != -1) { - intent.putExtra("chapter", index); - intent.putExtra("page", hs.getPage()); - } - } - mDialog.getContext().startActivity(intent); - } else { - HistoryProvider.getInstance(mDialog.getContext()).add(mManga, chapter.number, 0); - mDialog.getContext().startActivity(new Intent(mDialog.getContext(), ReadActivity2.class) - .putExtra("chapter", pos).putExtras(mManga.toBundle())); - } - mDialog.dismiss(); - } - - @Override - public boolean onChapterLongClick(int pos, MangaChapter chapter) { - return false; - } - - private static class LoadTask extends WeakAsyncTask, MangaSummary> { - - LoadTask(PreviewDialog object) { - super(object); - } - - @Override - protected void onPreExecute(@NonNull PreviewDialog dialog) { - } - - @SuppressWarnings("ConstantConditions") - @Override - protected MangaSummary doInBackground(Void... params) { - try { - //noinspection unchecked - MangaProvider provider = MangaProviderManager.instanceProvider(getObject().mDialog.getContext(), getObject().mManga.provider); - return provider.getDetailedInfo(getObject().mManga); - } catch (Exception e) { - return null; - } - } - - @Override - protected void onPostExecute(@NonNull final PreviewDialog dialog, MangaSummary mangaSummary) { - if (mangaSummary != null) { - dialog.mManga = mangaSummary; - if (mangaSummary.chapters.isEmpty()) { - dialog.mHolder.setText(R.string.no_chapters_found); - AnimUtils.crossfade(dialog.mProgressBar, dialog.mHolder); - } else { - dialog.mChaptersAdapter.setData(dialog.mManga.chapters); - dialog.mChaptersAdapter.setExtra(HistoryProvider.getInstance(dialog.mDialog.getContext()).get(dialog.mManga)); - dialog.mChaptersAdapter.notifyDataSetChanged(); - dialog.mRecyclerView.postDelayed(new Runnable() { - @Override - public void run() { - dialog.mRecyclerView.scrollToPosition(0); - } - }, 500); - AnimUtils.crossfade(dialog.mProgressBar, null); - } - } else { - dialog.mHolder.setText(R.string.loading_error); - AnimUtils.crossfade(dialog.mProgressBar, dialog.mHolder); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/RecommendationsPrefDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/RecommendationsPrefDialog.java deleted file mode 100644 index fa64965b..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/RecommendationsPrefDialog.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Checkable; -import android.widget.CheckedTextView; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.GenresSortAdapter; - -/** - * Created by nv95 on 22.06.16. - */ - -public class RecommendationsPrefDialog implements View.OnClickListener { - - private final Dialog mDialog; - @Nullable - private final GenresSortAdapter.Callback mCallback; - - public RecommendationsPrefDialog(final Context context, @Nullable GenresSortAdapter.Callback callback) { - @SuppressLint("InflateParams") - View contentView = LayoutInflater.from(context) - .inflate(R.layout.dialog_recommendprefs, null); - final CheckedTextView checkedTextViewFav = contentView.findViewById(R.id.checkedTextView_fav); - final CheckedTextView checkedTextViewHist = contentView.findViewById(R.id.checkedTextView_hist); - final CheckedTextView checkedTextViewMatch = contentView.findViewById(R.id.checkedTextView_match); - - checkedTextViewFav.setOnClickListener(this); - checkedTextViewHist.setOnClickListener(this); - checkedTextViewMatch.setOnClickListener(this); - - final SharedPreferences prefs = context.getSharedPreferences("recommendations", Context.MODE_PRIVATE); - checkedTextViewFav.setChecked(prefs.getBoolean("fav", true)); - checkedTextViewHist.setChecked(prefs.getBoolean("hist", true)); - checkedTextViewMatch.setChecked(prefs.getBoolean("match", false)); - - mDialog = new AlertDialog.Builder(context) - .setView(contentView) - .setCancelable(true) - .setTitle(R.string.recommendations_options) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - prefs.edit() - .putBoolean("fav", checkedTextViewFav.isChecked()) - .putBoolean("hist", checkedTextViewHist.isChecked()) - .putBoolean("match", checkedTextViewMatch.isChecked()) - .apply(); - if (mCallback != null) { - mCallback.onApply( - 0, checkedTextViewMatch.isChecked() ? 100 : 50, null, null - ); - } - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create(); - mCallback = callback; - } - - public void show() { - mDialog.show(); - } - - @Override - public void onClick(View v) { - if (v instanceof Checkable) { - ((Checkable)v).toggle(); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/StorageSelectDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/StorageSelectDialog.java deleted file mode 100644 index 8a8fa9e7..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/StorageSelectDialog.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.drawable.Drawable; -import android.support.v7.app.AlertDialog; -import android.view.View; -import android.widget.ImageView; -import android.widget.SimpleAdapter; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.StorageUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Created by nv95 on 22.11.16. - */ - -public class StorageSelectDialog implements DialogInterface.OnClickListener, SimpleAdapter.ViewBinder { - - private final AlertDialog mDialog; - private final SimpleAdapter mAdapter; - private final List mStorages; - private DirSelectDialog.OnDirSelectListener mDirSelectListener; - private final boolean mOnlyRoots; - - public StorageSelectDialog(Context context) { - this(context, false); - } - - public StorageSelectDialog(final Context context, boolean onlyRoots) { - mOnlyRoots = onlyRoots; - mStorages = StorageUtils.getAvailableStorages(context); - final Drawable[] icons = LayoutUtils.getThemedIcons( - context, - R.drawable.ic_storage_52, - R.drawable.ic_directory_52 - ); - ArrayList> data = new ArrayList<>( - mStorages.size() + 1); - Map m; - for (int i = 0; i < mStorages.size(); i++) { - m = new HashMap<>(); - m.put("title", mStorages.get(i).getName()); - m.put("subtitle", context.getString(R.string.size_free, StorageUtils.formatSizeMb(StorageUtils.getFreeSpaceMb(mStorages.get(i).getPath())))); - m.put("icon", icons[0]); - data.add(m); - } - if (!onlyRoots) { - m = new HashMap<>(); - m.put("title", context.getString(R.string.custom_path)); - m.put("subtitle", context.getString(R.string.pick_dir)); - m.put("icon", icons[1]); - data.add(m); - } - mAdapter = new SimpleAdapter( - context, - data, - R.layout.item_storage, - new String[]{"title", "subtitle", "icon"}, - new int[]{android.R.id.text1, android.R.id.text2, R.id.imageView} - ); - mAdapter.setViewBinder(this); - mDialog = new AlertDialog.Builder(context) - .setAdapter(mAdapter, this) - .setNegativeButton(android.R.string.cancel, null) - .setCancelable(true) - .setTitle(R.string.select_storage) - .create(); - } - - public StorageSelectDialog setDirSelectListener(DirSelectDialog.OnDirSelectListener dirSelectListener) { - this.mDirSelectListener = dirSelectListener; - return this; - } - - public void show() { - mDialog.show(); - } - - @Override - public void onClick(DialogInterface dialogInterface, int position) { - if (position >= mStorages.size()) { - new DirSelectDialog(mDialog.getContext()) - .setDirSelectListener(mDirSelectListener) - .show(); - } else if (mDirSelectListener != null) { - File dir = mStorages.get(position); - if (!mOnlyRoots) { - dir = StorageUtils.getFilesDir(mDialog.getContext(), dir, "saved"); - } - mDirSelectListener.onDirSelected(dir); - } - } - - @Override - public boolean setViewValue(View view, Object data, String s) { - if((view instanceof ImageView) & (data instanceof Drawable)) { - ((ImageView) view).setImageDrawable((Drawable) data); - return true; - } else { - return false; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/dialogs/ThumbnailsDialog.java b/app/src/main/java/org/nv95/openmanga/dialogs/ThumbnailsDialog.java deleted file mode 100644 index dc32b41b..00000000 --- a/app/src/main/java/org/nv95/openmanga/dialogs/ThumbnailsDialog.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.nv95.openmanga.dialogs; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.drawable.ColorDrawable; -import android.os.Build; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.Window; -import android.view.WindowManager; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.adapters.ThumbnailsAdapter; -import org.nv95.openmanga.components.reader.PageLoadListener; -import org.nv95.openmanga.components.reader.PageLoader; -import org.nv95.openmanga.components.reader.PageWrapper; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.utils.LayoutUtils; - -/** - * Created by nv95 on 18.11.16. - */ - -public class ThumbnailsDialog implements DialogInterface.OnDismissListener, PageLoadListener, NavigationListener { - - private final Dialog mDialog; - private final RecyclerView mRecyclerView; - private final PageLoader mLoader; - @Nullable - private ThumbnailsAdapter mAdapter; - @Nullable - private NavigationListener mNavigationListener; - - @SuppressLint("InflateParams") - public ThumbnailsDialog(Context context, PageLoader loader) { - mRecyclerView = (RecyclerView) LayoutInflater.from(context).inflate(R.layout.dialog_thumbs, null, false); - mRecyclerView.setLayoutManager(new GridLayoutManager(context, LayoutUtils.getOptimalColumnsCount(context.getResources(), ThumbSize.THUMB_SIZE_MEDIUM))); - mDialog = new Dialog(context, R.style.FullScreenDialog); - mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - mDialog.setContentView(mRecyclerView); - final int color = ContextCompat.getColor(context, R.color.transparent_dark); - final Window window = mDialog.getWindow(); - assert window != null; - window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - window.setBackgroundDrawable(new ColorDrawable(color)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - mRecyclerView.setFitsSystemWindows(true); - } - mDialog.setOnDismissListener(this); - mLoader = loader; - } - - public ThumbnailsDialog setNavigationListener(@Nullable NavigationListener listener) { - mNavigationListener = listener; - return this; - } - - public void show(int currentpos) { - mAdapter = new ThumbnailsAdapter(mLoader.getWrappersList()); - mAdapter.setNavigationListener(this); - mAdapter.setCurrentPosition(currentpos); - mRecyclerView.setAdapter(mAdapter); - mLoader.addListener(this); - mRecyclerView.scrollToPosition(currentpos); - mDialog.show(); - } - - @Override - public void onDismiss(DialogInterface dialogInterface) { - mLoader.removeListener(this); - } - - @Override - public void onLoadingStarted(PageWrapper page, boolean shadow) { - - } - - @Override - public void onProgressUpdated(PageWrapper page, boolean shadow, int percent) { - - } - - @Override - public void onLoadingComplete(PageWrapper page, boolean shadow) { - if (mAdapter != null) { - mAdapter.notifyItemChanged(page.position); - } - } - - @Override - public void onLoadingFail(PageWrapper page, boolean shadow) { - - } - - @Override - public void onLoadingCancelled(PageWrapper page, boolean shadow) { - - } - - @Override - public void onPageChange(int page) { - if (mNavigationListener != null) { - mDialog.dismiss(); - mNavigationListener.onPageChange(page); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/discover/DiscoverAdapter.java b/app/src/main/java/org/nv95/openmanga/discover/DiscoverAdapter.java new file mode 100644 index 00000000..af2fcc93 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/DiscoverAdapter.java @@ -0,0 +1,156 @@ +package org.nv95.openmanga.discover; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.IntDef; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.ProviderHeader; +import org.nv95.openmanga.discover.bookmarks.BookmarksListActivity; +import org.nv95.openmanga.filepicker.FilePickerActivity; +import org.nv95.openmanga.mangalist.MangaListActivity; +import org.nv95.openmanga.recommendations.RecommendationsActivity; +import org.nv95.openmanga.storage.SavedMangaActivity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class DiscoverAdapter extends RecyclerView.Adapter implements View.OnClickListener { + + private final ArrayList mDataset; + + public DiscoverAdapter(ArrayList dataset) { + mDataset = dataset; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, @ItemViewType int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + RecyclerView.ViewHolder holder; + switch (viewType) { + case ItemViewType.TYPE_ITEM_WITH_ICON: + holder = new DetailsProviderHolder(inflater.inflate(R.layout.item_two_lines_icon, parent, false)); + break; + case ItemViewType.TYPE_HEADER: + return new HeaderHolder(inflater.inflate(R.layout.header_group, parent, false)); + case ItemViewType.TYPE_ITEM_DEFAULT: + holder = new ProviderHolder(inflater.inflate(R.layout.item_single_line, parent, false)); + break; + default: + throw new AssertionError("Unknown viewType"); + } + holder.itemView.setOnClickListener(this); + return holder; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof DetailsProviderHolder) { + ProviderHeaderDetailed item = (ProviderHeaderDetailed) mDataset.get(position); + ((DetailsProviderHolder) holder).text1.setText(item.dName); + holder.itemView.setTag(item.cName); + ((DetailsProviderHolder) holder).icon.setImageDrawable(item.icon); + ((DetailsProviderHolder) holder).text2.setText(item.summary); + } else if (holder instanceof ProviderHolder) { + ProviderHeader item = (ProviderHeader) mDataset.get(position); + holder.itemView.setTag(item.cName); + ((ProviderHolder) holder).text1.setText(item.dName); + } else if (holder instanceof HeaderHolder) { + ((HeaderHolder) holder).textView.setText((String) mDataset.get(position)); + } + } + + @ItemViewType + @Override + public int getItemViewType(int position) { + final Object item = mDataset.get(position); + if (item instanceof String) { + return ItemViewType.TYPE_HEADER; + } else if (item instanceof ProviderHeaderDetailed) { + return ItemViewType.TYPE_ITEM_WITH_ICON; + } else if (item instanceof ProviderHeader) { + return ItemViewType.TYPE_ITEM_DEFAULT; + } else { + throw new AssertionError("Unknown ItemViewType"); + } + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public void onClick(View view) { + final String cname = String.valueOf(view.getTag()); + final Context context = view.getContext(); + switch (cname) { + case SpecificCName.BROWSE_IMPORT: + context.startActivity(new Intent(context.getApplicationContext(), FilePickerActivity.class)); + break; + case SpecificCName.BROWSE_SAVED: + context.startActivity(new Intent(context.getApplicationContext(), SavedMangaActivity.class)); + break; + case SpecificCName.BROWSE_RECOMMENDATIONS: + context.startActivity(new Intent(context.getApplicationContext(), RecommendationsActivity.class)); + break; + case SpecificCName.BROWSE_BOOKMARKS: + context.startActivity(new Intent(context.getApplicationContext(), BookmarksListActivity.class)); + break; + default: + context.startActivity(new Intent(context.getApplicationContext(), MangaListActivity.class) + .putExtra("provider.cname", cname)); + } + } + + class ProviderHolder extends RecyclerView.ViewHolder { + + final TextView text1; + + ProviderHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + } + } + + class DetailsProviderHolder extends ProviderHolder { + + final TextView text2; + final ImageView icon; + + DetailsProviderHolder(View itemView) { + super(itemView); + text2 = itemView.findViewById(android.R.id.text2); + icon = itemView.findViewById(android.R.id.icon); + } + } + + class HeaderHolder extends RecyclerView.ViewHolder { + + private final TextView textView; + + public HeaderHolder(View itemView) { + super(itemView); + textView = itemView.findViewById(R.id.textView); + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ItemViewType.TYPE_ITEM_DEFAULT, ItemViewType.TYPE_ITEM_WITH_ICON, ItemViewType.TYPE_HEADER}) + public @interface ItemViewType { + int TYPE_ITEM_DEFAULT = 0; + int TYPE_ITEM_WITH_ICON = 1; + int TYPE_HEADER = 3; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/DiscoverFragment.java b/app/src/main/java/org/nv95/openmanga/discover/DiscoverFragment.java new file mode 100644 index 00000000..7b951014 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/DiscoverFragment.java @@ -0,0 +1,105 @@ +package org.nv95.openmanga.discover; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Layout; +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 org.nv95.openmanga.AppBaseFragment; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.utils.ThemeUtils; +import org.nv95.openmanga.common.views.recyclerview.HeaderDividerItemDecoration; +import org.nv95.openmanga.core.storage.ProvidersStore; +import org.nv95.openmanga.tools.settings.providers.ProvidersSettingsActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class DiscoverFragment extends AppBaseFragment { + + private static final int REQUEST_PROVIDERS_CONFIG = 12; + + private RecyclerView mRecyclerView; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return super.onCreateView(inflater, container, R.layout.recyclerview); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = view.findViewById(R.id.recyclerView); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(new LinearLayoutManager(view.getContext())); + mRecyclerView.addItemDecoration(new HeaderDividerItemDecoration(view.getContext())); + mRecyclerView.setClipToPadding(false); + mRecyclerView.setPadding( + mRecyclerView.getPaddingLeft(), + mRecyclerView.getPaddingTop(), + mRecyclerView.getPaddingRight(), + mRecyclerView.getPaddingBottom() + ThemeUtils.getAttrSizePx(mRecyclerView.getContext(), android.R.attr.actionBarSize) + ); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final ArrayList dataset = new ArrayList<>(); + dataset.add(new ProviderHeaderDetailed(SpecificCName.BROWSE_IMPORT, getString(R.string.browse_filesystem), + getString(R.string.import_cbz_summary), ContextCompat.getDrawable(getActivity(), R.drawable.ic_folder_white))); + dataset.add(new ProviderHeaderDetailed(SpecificCName.BROWSE_SAVED, getString(R.string.saved_manga), + getString(R.string.saved_manga_summary), ContextCompat.getDrawable(getActivity(), R.drawable.ic_sdcard_white))); + dataset.add(new ProviderHeaderDetailed(SpecificCName.BROWSE_RECOMMENDATIONS, getString(R.string.recommendations), + getString(R.string.recommendations_summary), ContextCompat.getDrawable(getActivity(), R.drawable.ic_lightbulb_white))); + dataset.add(new ProviderHeaderDetailed(SpecificCName.BROWSE_BOOKMARKS, getString(R.string.bookmarks), + getString(R.string.bookmarks_summary), ContextCompat.getDrawable(getActivity(), R.drawable.ic_bookmark_white))); + dataset.add(getString(R.string.storage_remote)); + dataset.addAll(new ProvidersStore(getActivity()).getUserProviders()); + final DiscoverAdapter adapter = new DiscoverAdapter(dataset); + mRecyclerView.setAdapter(adapter); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.options_discover, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_configure_providers: + startActivityForResult(new Intent(getActivity(), ProvidersSettingsActivity.class), REQUEST_PROVIDERS_CONFIG); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_PROVIDERS_CONFIG) { + onActivityCreated(null); + } + } + + @Override + public void scrollToTop() { + mRecyclerView.smoothScrollToPosition(0); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/ProviderHeaderDetailed.java b/app/src/main/java/org/nv95/openmanga/discover/ProviderHeaderDetailed.java new file mode 100644 index 00000000..e7d503f6 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/ProviderHeaderDetailed.java @@ -0,0 +1,21 @@ +package org.nv95.openmanga.discover; + +import android.graphics.drawable.Drawable; + +import org.nv95.openmanga.core.models.ProviderHeader; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class ProviderHeaderDetailed extends ProviderHeader { + + public final String summary; + public final Drawable icon; + + public ProviderHeaderDetailed(String cname, String dname, String summary, Drawable icon) { + super(cname, dname); + this.summary = summary; + this.icon = icon; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/SpecificCName.java b/app/src/main/java/org/nv95/openmanga/discover/SpecificCName.java new file mode 100644 index 00000000..eb8cd9f4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/SpecificCName.java @@ -0,0 +1,16 @@ +package org.nv95.openmanga.discover; + +/** + * Created by koitharu on 26.01.18. + */ + +interface SpecificCName { + + String BROWSE_IMPORT = "browse/import"; + + String BROWSE_SAVED = "browse/saved"; + + String BROWSE_RECOMMENDATIONS = "browse/recommendations"; + + String BROWSE_BOOKMARKS = "browse/bookmarks"; +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarkRemoveTask.java b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarkRemoveTask.java new file mode 100644 index 00000000..035407cd --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarkRemoveTask.java @@ -0,0 +1,51 @@ +package org.nv95.openmanga.discover.bookmarks; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.widget.Toast; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.storage.db.BookmarksRepository; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; + +/** + * Created by koitharu on 29.01.18. + */ + +public class BookmarkRemoveTask extends WeakAsyncTask { + + public BookmarkRemoveTask(Context context) { + super(context); + } + + @Override + protected MangaBookmark doInBackground(MangaBookmark... bookmarks) { + try { + if (BookmarksRepository.get(getObject()).remove(bookmarks[0])) { + new ThumbnailsStorage(getObject()).remove(bookmarks[0]); + return bookmarks[0]; + } else { + return null; + } + } catch (Exception e) { + return null; + } + } + + @Override + protected void onPostExecute(@NonNull Context context, MangaBookmark bookmark) { + super.onPostExecute(context, bookmark); + if (bookmark == null) { + Toast.makeText(context, R.string.error_occurred, Toast.LENGTH_SHORT).show(); + } else if (context instanceof OnBookmarkRemovedListener) { + ((OnBookmarkRemovedListener) context).onBookmarkRemoved(bookmark); + } + } + + public interface OnBookmarkRemovedListener { + + void onBookmarkRemoved(@NonNull MangaBookmark bookmark); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarkSpanSizeLookup.java b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarkSpanSizeLookup.java new file mode 100644 index 00000000..e522fdad --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarkSpanSizeLookup.java @@ -0,0 +1,29 @@ +package org.nv95.openmanga.discover.bookmarks; + +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; + +/** + * Created by koitharu on 29.01.18. + */ + +public class BookmarkSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { + + private final int mMaxSpans; + private final RecyclerView.Adapter mAdapter; + + public BookmarkSpanSizeLookup(RecyclerView.Adapter adapter, int maxSpans) { + mAdapter = adapter; + mMaxSpans = maxSpans; + } + + @Override + public int getSpanSize(int position) { + switch (mAdapter.getItemViewType(position)) { + case BookmarksListAdapter.ItemViewType.TYPE_ITEM_HEADER: + return mMaxSpans; + default: + return 1; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksListActivity.java b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksListActivity.java new file mode 100644 index 00000000..f8fa1b41 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksListActivity.java @@ -0,0 +1,107 @@ +package org.nv95.openmanga.discover.bookmarks; + +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.MetricsUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.views.recyclerview.SpaceItemDecoration; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.models.UniqueObject; +import org.nv95.openmanga.core.storage.db.BookmarkSpecification; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; + +import java.util.ArrayList; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class BookmarksListActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks>, + BookmarkRemoveTask.OnBookmarkRemovedListener { + + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + private BookmarksListAdapter mAdapter; + private ArrayList mDataset; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bookmarks); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mProgressBar = findViewById(R.id.progressBar); + mRecyclerView = findViewById(R.id.recyclerView); + mTextViewHolder = findViewById(R.id.textView_holder); + mRecyclerView.setHasFixedSize(true); + + final int spans = MetricsUtils.getPreferredColumnsCountMedium(getResources()); + final GridLayoutManager layoutManager = new GridLayoutManager(this, spans); + mRecyclerView.addItemDecoration(new SpaceItemDecoration(ResourceUtils.dpToPx(getResources(), 1))); + mRecyclerView.setLayoutManager(layoutManager); + + mDataset = new ArrayList<>(); + mAdapter = new BookmarksListAdapter(mDataset, new ThumbnailsStorage(this)); + mRecyclerView.setAdapter(mAdapter); + layoutManager.setSpanSizeLookup(new BookmarkSpanSizeLookup(mAdapter, spans)); + + getLoaderManager().initLoader(0, new BookmarkSpecification().orderByMangaAndDate(true).toBundle(), this).forceLoad(); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new BookmarksLoader(this, BookmarkSpecification.from(args)); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper result) { + mProgressBar.setVisibility(View.GONE); + if (result.isSuccess()) { + final ArrayList list = result.get(); + mDataset.clear(); + long prevId = 0; + for (MangaBookmark o : list) { + if (prevId != o.manga.id) { + mDataset.add(o.manga); + } + prevId = o.manga.id; + mDataset.add(o); + } + mAdapter.notifyDataSetChanged(); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + } else { + Snackbar.make(mRecyclerView, ErrorUtils.getErrorMessage(result.getError()), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public void onBookmarkRemoved(@NonNull MangaBookmark bookmark) { + mDataset.remove(bookmark); + mAdapter.notifyDataSetChanged(); + Snackbar.make(mRecyclerView, R.string.bookmark_removed, Snackbar.LENGTH_SHORT).show(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksListAdapter.java b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksListAdapter.java new file mode 100644 index 00000000..57a481d8 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksListAdapter.java @@ -0,0 +1,195 @@ +package org.nv95.openmanga.discover.bookmarks; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.IntDef; +import android.support.v7.widget.PopupMenu; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.IntentUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.UniqueObject; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; +import org.nv95.openmanga.preview.PreviewActivity; +import org.nv95.openmanga.reader.ReaderActivity; +import org.nv95.openmanga.reader.ToolButtonCompat; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Created by koitharu on 29.01.18. + */ + +final class BookmarksListAdapter extends RecyclerView.Adapter> { + + private final ArrayList mDataset; + private final ThumbnailsStorage mThumbStore; + + BookmarksListAdapter(ArrayList dataset, ThumbnailsStorage thumbnailsStorage) { + mDataset = dataset; + mThumbStore = thumbnailsStorage; + setHasStableIds(true); + } + + @Override + public DataViewHolder onCreateViewHolder(ViewGroup parent, @ItemViewType int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + switch (viewType) { + case ItemViewType.TYPE_ITEM_BOOKMARK: + return new BookmarkHolder(inflater.inflate(R.layout.item_bookmark_grid, parent, false)); + case ItemViewType.TYPE_ITEM_HEADER: + return new HeaderHolder(inflater.inflate(R.layout.header_group_button, parent, false)); + default: + throw new RuntimeException("Invalid ItemViewType"); + } + } + + @Override + public void onBindViewHolder(DataViewHolder holder, int position) { + if (holder instanceof HeaderHolder) { + ((HeaderHolder) holder).bind((MangaHeader) mDataset.get(position)); + } else if (holder instanceof BookmarkHolder) { + ((BookmarkHolder) holder).bind((MangaBookmark) mDataset.get(position)); + } + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + @ItemViewType + public int getItemViewType(int position) { + return mDataset.get(position) instanceof MangaHeader ? ItemViewType.TYPE_ITEM_HEADER : ItemViewType.TYPE_ITEM_BOOKMARK; + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).getId(); + } + + static class HeaderHolder extends DataViewHolder implements View.OnClickListener { + + private final TextView mTextView; + private final Button mButtonMore; + + HeaderHolder(View itemView) { + super(itemView); + mTextView = itemView.findViewById(R.id.textView); + mButtonMore = itemView.findViewById(R.id.button_more); + mButtonMore.setOnClickListener(this); + } + + @Override + public void bind(MangaHeader mangaHeader) { + super.bind(mangaHeader); + mTextView.setText(mangaHeader.name); + } + + @Override + public void onClick(View v) { + final MangaHeader data = getData(); + if (data == null) { + return; + } + final Context context = v.getContext(); + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", data)); + } + } + + class BookmarkHolder extends DataViewHolder implements View.OnClickListener, + View.OnLongClickListener, PopupMenu.OnMenuItemClickListener { + + private final ImageView mImageView; + private final TextView mTextView; + private final ToolButtonCompat mToolButtonMenu; + private final PopupMenu mPopupMenu; + + BookmarkHolder(View itemView) { + super(itemView); + mTextView = itemView.findViewById(R.id.textView); + mImageView = itemView.findViewById(R.id.imageView); + mToolButtonMenu = itemView.findViewById(R.id.toolButton_menu); + mPopupMenu = new PopupMenu(itemView.getContext(), mToolButtonMenu); + mPopupMenu.inflate(R.menu.popup_bookmark); + mPopupMenu.setOnMenuItemClickListener(this); + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + mToolButtonMenu.setOnClickListener(this); + } + + @Override + public void bind(MangaBookmark bookmark) { + super.bind(bookmark); + ImageUtils.setThumbnail(mImageView, mThumbStore.getFile(bookmark)); + mTextView.setText(ResourceUtils.formatTimeRelative(bookmark.createdAt)); + } + + @Override + public void recycle() { + ImageUtils.recycle(mImageView); + super.recycle(); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.toolButton_menu: + mPopupMenu.show(); + break; + default: + final Context context = view.getContext(); + context.startActivity(new Intent(context, ReaderActivity.class) + .setAction(ReaderActivity.ACTION_BOOKMARK_OPEN) + .putExtra("bookmark", getData())); + } + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + final MangaBookmark data = getData(); + if (data == null) { + return false; + } + switch (item.getItemId()) { + case R.id.action_remove: + new BookmarkRemoveTask(itemView.getContext()).start(data); + return true; + case R.id.action_shortcut: + IntentUtils.createLauncherShortcutRead(itemView.getContext().getApplicationContext(), data); + return true; + default: + return false; + } + } + + @Override + public boolean onLongClick(View v) { + onClick(mToolButtonMenu); + return true; + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ItemViewType.TYPE_ITEM_BOOKMARK, ItemViewType.TYPE_ITEM_HEADER}) + public @interface ItemViewType { + int TYPE_ITEM_BOOKMARK = 0; + int TYPE_ITEM_HEADER = 1; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksLoader.java b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksLoader.java new file mode 100644 index 00000000..3905254c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/discover/bookmarks/BookmarksLoader.java @@ -0,0 +1,32 @@ +package org.nv95.openmanga.discover.bookmarks; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.storage.db.BookmarkSpecification; +import org.nv95.openmanga.core.storage.db.BookmarksRepository; + +import java.util.ArrayList; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class BookmarksLoader extends AsyncTaskLoader> { + + private final BookmarkSpecification mSpec; + + public BookmarksLoader(Context context, BookmarkSpecification specification) { + super(context); + mSpec = specification; + } + + @Override + public ListWrapper loadInBackground() { + final BookmarksRepository repo = BookmarksRepository.get(getContext()); + final ArrayList list = repo.query(mSpec); + return list == null ? ListWrapper.badList() : new ListWrapper<>(list); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/filepicker/FileListLoader.java b/app/src/main/java/org/nv95/openmanga/filepicker/FileListLoader.java new file mode 100644 index 00000000..dfd2d535 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/filepicker/FileListLoader.java @@ -0,0 +1,83 @@ +package org.nv95.openmanga.filepicker; + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.support.annotation.NonNull; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.FileDesc; +import org.nv95.openmanga.core.providers.ZipArchiveProvider; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +final class FileListLoader extends AsyncTaskLoader> { + + private final String mPath; + private final boolean mSupportedOnly; + + FileListLoader(Context context, String path, boolean supportedOnly) { + super(context); + mPath = path; + mSupportedOnly = supportedOnly; + } + + @NonNull + @Override + public ListWrapper loadInBackground() { + try { + File[] files = new File(mPath).listFiles(); + if (files == null) { + return ListWrapper.badList(); + } + final ArrayList result = new ArrayList<>(); + for (File o : files) { + if (o.isDirectory()) { + final String[] sub = o.list(); + result.add(new FileDesc( + o, + sub == null ? 0 : sub.length + )); + } else if (!mSupportedOnly || ZipArchiveProvider.isFileSupported(o)) { + result.add(new FileDesc( + o, + 0 + )); + } + } + Collections.sort(result, new FileNameComparator()); + Collections.sort(result, new FileTypeComparator()); + return new ListWrapper<>(result); + } catch (Exception e) { + return new ListWrapper<>(e); + } + } + private class FileTypeComparator implements Comparator { + + @Override + public int compare(FileDesc file1, FileDesc file2) { + + if (file1.file.isDirectory() && file2.file.isFile()) + return -1; + if (file1.file.isDirectory() && file2.file.isDirectory()) { + return 0; + } + if (file1.file.isFile() && file2.file.isFile()) { + return 0; + } + return 1; + } + } + + private class FileNameComparator implements Comparator { + + @Override + public int compare(FileDesc file1, FileDesc file2) { + + return String.CASE_INSENSITIVE_ORDER.compare(file1.file.getName(), + file2.file.getName()); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/filepicker/FilePickerActivity.java b/app/src/main/java/org/nv95/openmanga/filepicker/FilePickerActivity.java new file mode 100644 index 00000000..a2f67111 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/filepicker/FilePickerActivity.java @@ -0,0 +1,228 @@ +package org.nv95.openmanga.filepicker; + +import android.Manifest; +import android.app.LoaderManager; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Loader; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.ContentLoadingProgressBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.FileDesc; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.providers.ZipArchiveProvider; +import org.nv95.openmanga.core.storage.FlagsStorage; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.io.File; +import java.util.ArrayList; + +public final class FilePickerActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks>, + OnFileSelectListener { + + private static final int REQUEST_CODE_PERMISSION = 14; + + private RecyclerView mRecyclerView; + private TextView mTextViewHolder; + private ContentLoadingProgressBar mProgressBar; + private final ArrayList mDataset = new ArrayList<>(); + private FilePickerAdapter mAdapter; + private File mRoot; + private boolean mFilterFiles; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_filepicker); + setSupportActionBar(R.id.toolbar); + enableHomeAsClose(); + + mTextViewHolder = findViewById(R.id.textView_holder); + mProgressBar = findViewById(R.id.progressBar); + mRecyclerView = findViewById(R.id.recyclerView); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(ResourceUtils.isLandscape(getResources()) ? + new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) + : new LinearLayoutManager(this)); + mAdapter = new FilePickerAdapter(mDataset, this); + mRecyclerView.setAdapter(mAdapter); + + mFilterFiles = FlagsStorage.get(this).isPickerFilterFiles(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + checkPermissions(REQUEST_CODE_PERMISSION, Manifest.permission.READ_EXTERNAL_STORAGE); + } else { + onPermissionGranted(REQUEST_CODE_PERMISSION, null); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mRecyclerView.setLayoutManager(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? + new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) + : new LinearLayoutManager(this)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_file_picker, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.option_filter).setChecked(mFilterFiles); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.option_filter: + mFilterFiles = !item.isChecked(); + item.setChecked(mFilterFiles); + FlagsStorage.get(this).setPickerFilterFiles(mFilterFiles); + onFileSelected(mRoot); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @NonNull + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new FileListLoader(this, args.getString("path"), args.getBoolean("filter")); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper data) { + mDataset.clear(); + mProgressBar.hide(); + if (data.isSuccess()) { + mDataset.addAll(data.get()); + mTextViewHolder.setVisibility(mDataset.isEmpty() ? View.VISIBLE : View.GONE); + FlagsStorage.get(this).setLastPickerDir(mRoot); + } else { + mTextViewHolder.setVisibility(View.VISIBLE); + } + mAdapter.notifyDataSetChanged(); + LayoutUtils.setSelectionFromTop(mRecyclerView, 0); + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + protected void onPermissionGranted(int requestCode, String permission) { + mProgressBar.show(); + mRoot = FlagsStorage.get(this).getLastPickerRoot(Environment.getExternalStorageDirectory()); + setSubtitle(mRoot.getPath()); + final Bundle args = new Bundle(2); + args.putString("path", mRoot.getAbsolutePath()); + args.putBoolean("filter", mFilterFiles); + getLoaderManager().initLoader(0, args, this).forceLoad(); + } + + @Override + public void onFileSelected(@NonNull File file) { + if (file.isDirectory()) { + mRoot = file; + setSubtitle(mRoot.getPath()); + mProgressBar.show(); + mTextViewHolder.setVisibility(View.GONE); + final Bundle args = new Bundle(2); + args.putString("path", file.getAbsolutePath()); + args.putBoolean("filter", mFilterFiles); + getLoaderManager().restartLoader(0, args, this).forceLoad(); + } else if (file.isFile()) { + new MangaOpenTask(this) + .start(Uri.fromFile(file)); + } + } + + @Override + public void onBackPressed() { + mRoot = mRoot.getParentFile(); + if (mRoot != null && mRoot.exists() && mRoot.canRead()) { + onFileSelected(mRoot); + } else { + super.onBackPressed(); + } + } + + private static class MangaOpenTask extends WeakAsyncTask + implements DialogInterface.OnCancelListener { + + private final ProgressDialog mProgressDialog; + + MangaOpenTask(FilePickerActivity filePickerActivity) { + super(filePickerActivity); + mProgressDialog = new ProgressDialog(filePickerActivity); + mProgressDialog.setCancelable(true); + mProgressDialog.setOnCancelListener(this); + mProgressDialog.setMessage(filePickerActivity.getString(R.string.loading)); + } + + @Override + protected void onPreExecute(@NonNull FilePickerActivity filePickerActivity) { + mProgressDialog.show(); + } + + @Override + protected MangaHeader doInBackground(Uri... uris) { + try { + return ZipArchiveProvider.getManga(getObject(), uris[0]); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + protected void onPostExecute(@NonNull FilePickerActivity filePickerActivity, MangaHeader manga) { + mProgressDialog.dismiss(); + if (manga == null) { + new AlertDialog.Builder(filePickerActivity) + .setMessage(R.string.invalid_file_not_supported) + .setNegativeButton(R.string.close, null) + .create() + .show(); + } else { + filePickerActivity.startActivity(new Intent(filePickerActivity, PreviewActivity.class) + .putExtra("manga", manga)); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + if (canCancel()) { + cancel(false); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/filepicker/FilePickerAdapter.java b/app/src/main/java/org/nv95/openmanga/filepicker/FilePickerAdapter.java new file mode 100644 index 00000000..c1b01237 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/filepicker/FilePickerAdapter.java @@ -0,0 +1,143 @@ +package org.nv95.openmanga.filepicker; + +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.graphics.drawable.RoundedBitmapDrawable; +import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.MetricsUtils; +import org.nv95.openmanga.core.models.FileDesc; +import org.nv95.openmanga.core.providers.ZipArchiveProvider; + +import java.io.File; +import java.util.ArrayList; + +final class FilePickerAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private final OnFileSelectListener mListener; + + FilePickerAdapter(ArrayList dataset, OnFileSelectListener listener) { + mDataset = dataset; + mListener = listener; + } + + @Override + public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new FileViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_two_lines_icon, parent, false)); + } + + @Override + public void onBindViewHolder(FileViewHolder holder, int position) { + holder.bind(mDataset.get(position)); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + class FileViewHolder extends DataViewHolder implements View.OnClickListener { + + private final ImageView mIcon; + private final TextView mText1; + private final TextView mText2; + + FileViewHolder(View itemView) { + super(itemView); + mIcon = itemView.findViewById(android.R.id.icon); + mText1 = itemView.findViewById(android.R.id.text1); + mText2 = itemView.findViewById(android.R.id.text2); + itemView.setOnClickListener(this); + } + + @Override + public void bind(FileDesc desc) { + super.bind(desc); + if (desc.file.isDirectory()) { + mIcon.setImageResource(R.drawable.ic_folder_white); + mText2.setText(itemView.getResources().getQuantityString( + R.plurals.files_count, + desc.entryCount, + desc.entryCount + )); + mText2.setVisibility(View.VISIBLE); + } else { + if (ZipArchiveProvider.isFileSupported(desc.file)) { + new ThumbnailTask(this, desc.file.getAbsolutePath().hashCode()) + .start(desc.file); + } else { + mIcon.setImageResource(R.drawable.ic_file_white); + } + mText2.setVisibility(View.GONE); + } + mText1.setText(desc.file.getName()); + } + + @Override + public void onClick(View v) { + final FileDesc desc = getData(); + if (desc != null) { + mListener.onFileSelected(desc.file); + } + } + } + + private static class ThumbnailTask extends WeakAsyncTask { + + private final Integer mId; + private final MetricsUtils.Size mSize; + + public ThumbnailTask(FileViewHolder fileViewHolder, int id) { + super(fileViewHolder); + fileViewHolder.mIcon.setImageResource(R.drawable.ic_file_image_white); + mId = id; + fileViewHolder.mIcon.setTag(mId); + mSize = MetricsUtils.Size.fromLayoutParams(fileViewHolder.mIcon); + } + + @Nullable + @Override + protected Drawable doInBackground(File... files) { + try { + final File thumb = ZipArchiveProvider.createThumbnail( + getObject().itemView.getContext(), + Uri.fromFile(files[0]), + mSize + ); + if (thumb == null) { + return null; + } else { + RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create( + getObject().itemView.getResources(), + thumb.getPath() + ); + drawable.setCornerRadius(mSize.width / 2.f); + return drawable; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + protected void onPostExecute(@NonNull FileViewHolder fileViewHolder, @Nullable Drawable drawable) { + if (mId.equals(fileViewHolder.mIcon.getTag()) && drawable != null) { + fileViewHolder.mIcon.setImageDrawable(drawable); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/filepicker/OnFileSelectListener.java b/app/src/main/java/org/nv95/openmanga/filepicker/OnFileSelectListener.java new file mode 100644 index 00000000..d42fdb36 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/filepicker/OnFileSelectListener.java @@ -0,0 +1,10 @@ +package org.nv95.openmanga.filepicker; + +import android.support.annotation.NonNull; + +import java.io.File; + +interface OnFileSelectListener { + + void onFileSelected(@NonNull File file); +} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/BrightnessHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/BrightnessHelper.java deleted file mode 100644 index cfd9d14f..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/BrightnessHelper.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.view.Window; -import android.view.WindowManager; - -/** - * Created by nv95 on 12.02.16. - */ -public class BrightnessHelper { - - private final Window mWindow; - private final float mLastValue; - - public BrightnessHelper(Window window) { - this.mWindow = window; - mLastValue = mWindow.getAttributes().screenBrightness; - } - - public void setBrightness(int percent) { - WindowManager.LayoutParams lp = mWindow.getAttributes(); - lp.screenBrightness = percent / 100f; - mWindow.setAttributes(lp); - } - - public void reset() { - WindowManager.LayoutParams lp = mWindow.getAttributes(); - lp.screenBrightness = mLastValue; - mWindow.setAttributes(lp); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/ContentShareHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/ContentShareHelper.java deleted file mode 100644 index 5d246598..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/ContentShareHelper.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; -import android.media.ThumbnailUtils; -import android.net.Uri; -import android.view.MenuItem; -import android.view.SubMenu; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.PreviewActivity2; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.LayoutUtils; - -import java.io.File; -import java.util.List; - -/** - * Created by nv95 on 28.01.16. - */ -public class ContentShareHelper { - - private final Context mContext; - private final Intent mIntent; - - public ContentShareHelper(Context context) { - mContext = context; - mIntent = new Intent(Intent.ACTION_SEND); - } - - public void share(MangaInfo manga) { - mIntent.setType("text/plain"); - String path = null; - if (manga.provider == LocalMangaProvider.class) { - path = LocalMangaProvider.getInstance(mContext).getSourceUrl(manga.id); - } - if (path == null) { - path = manga.path; - } - mIntent.putExtra(Intent.EXTRA_TEXT, path); - mIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, manga.name); - mContext.startActivity(Intent.createChooser(mIntent, mContext.getString(R.string.action_share))); - } - - public void shareImage(File file) { - mIntent.setType("image/*"); - mIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); - mContext.startActivity(Intent.createChooser(mIntent, mContext.getString(R.string.action_share))); - } - - public void exportFile(File file) { - mIntent.setType("file/*"); - mIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); - mContext.startActivity(Intent.createChooser(mIntent, mContext.getString(R.string.export_file))); - } - - public void createShortcut(MangaInfo manga) { - Intent shortcutIntent = new Intent(mContext, PreviewActivity2.class); - shortcutIntent.setAction("org.nv95.openmanga.action.PREVIEW"); - shortcutIntent.putExtras(manga.toBundle()); - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - Intent addIntent = new Intent(); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, manga.name); - - Bitmap cover = ImageUtils.getCachedImage(manga.preview); - if (cover == null) { - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher)); - } else { - final int size = LayoutUtils.getLauncherIconSize(mContext); - cover = ThumbnailUtils.extractThumbnail(cover, size, size, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, cover); - } - addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - mContext.getApplicationContext().sendBroadcast(addIntent); - } - - public void buildOpenWithSubmenu(MangaInfo manga, MenuItem menuItem) { - SubMenu menu = menuItem.getSubMenu(); - Uri uri = Uri.parse(manga.path); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - PackageManager pm = mContext.getPackageManager(); - List allActivities = pm.queryIntentActivities(intent, 0); - if (allActivities.isEmpty()) { - menuItem.setVisible(false); - } else { - menuItem.setVisible(true); - menu.clear(); - for (ResolveInfo o : allActivities) { - MenuItem item = menu.add(o.loadLabel(pm)); - item.setIcon(o.loadIcon(pm)); - ComponentName name = new ComponentName(o.activityInfo.applicationInfo.packageName, - o.activityInfo.name); - Intent i = new Intent(intent); - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - i.setComponent(name); - item.setIntent(i); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/DirRemoveHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/DirRemoveHelper.java deleted file mode 100644 index 9b720b1f..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/DirRemoveHelper.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2016 Vasily Nikitin - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see http://www.gnu.org/licenses/. - */ - -package org.nv95.openmanga.helpers; - -import android.support.annotation.Nullable; -import android.util.Log; - -import java.io.File; -import java.io.FilenameFilter; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.regex.Pattern; - -/** - * Created by nv95 on 03.01.16. - * Remove directory - */ -public class DirRemoveHelper implements Runnable { - - private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); - - @Nullable - private final File[] mFiles; - - public DirRemoveHelper(File file) { - mFiles = new File[]{file}; - } - - @SuppressWarnings("NullableProblems") - public DirRemoveHelper(File files[]) { - mFiles = files; - } - - public DirRemoveHelper(File dir, String regexp) { - final Pattern pattern = Pattern.compile(regexp); - mFiles = dir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String filename) { - return pattern.matcher(filename).matches(); - } - }); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private static void removeDir(File dir) { - if (dir == null || !dir.exists()) { - Log.w("DIRRM", "not exists: " + (dir != null ? dir.getPath() : "null")); - return; - } - if (dir.isDirectory()) { - for (File o : dir.listFiles()) { - if (o.isDirectory()) { - removeDir(o); - } else { - o.delete(); - } - } - } - Log.d("DIRRM", "removed: " + dir.getPath()); - dir.delete(); - } - - @Override - public void run() { - if (mFiles == null) { - return; - } - for (File file : mFiles) { - removeDir(file); - } - } - - public void runAsync() { - EXECUTOR.execute(this); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/ListModeHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/ListModeHelper.java deleted file mode 100644 index 44353d81..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/ListModeHelper.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.view.Menu; -import android.view.MenuItem; - -import org.nv95.openmanga.R; - -/** - * Created by nv95 on 09.02.16. - */ -public class ListModeHelper implements SharedPreferences.OnSharedPreferenceChangeListener { - - private static final String PREF_KEY = "view_mode"; - - private static final int LISTMODE_LIST = 0; - private static final int LISTMODE_GRID_SMALL = 1; - private static final int LISTMODE_GRID_MEDIUM = 2; - private static final int LISTMODE_GRID_LARGE = 3; - - private static final int[] ITEMS_IDS = new int[] { - R.id.listmode_list, - R.id.listmode_grid_small, - R.id.listmode_grid_medium, - R.id.listmode_grid_large - }; - - private final SharedPreferences mPreferences; - private final OnListModeListener mListModeListener; - - public ListModeHelper(final Context context, OnListModeListener callback) { - mListModeListener = callback; - mPreferences = PreferenceManager.getDefaultSharedPreferences(context); - } - - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem item = menu.findItem(R.id.action_listmode); - if (item == null) { - return false; - } - Menu submenu = item.getSubMenu(); - if (submenu == null) { - return false; - } - int mode = mPreferences.getInt(PREF_KEY, 0); - submenu.findItem(ITEMS_IDS[mode]).setChecked(true); - return true; - } - - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.listmode_list: - mPreferences.edit().putInt(PREF_KEY, LISTMODE_LIST).apply(); - return true; - case R.id.listmode_grid_small: - mPreferences.edit().putInt(PREF_KEY, LISTMODE_GRID_SMALL).apply(); - return true; - case R.id.listmode_grid_medium: - mPreferences.edit().putInt(PREF_KEY, LISTMODE_GRID_MEDIUM).apply(); - return true; - case R.id.listmode_grid_large: - mPreferences.edit().putInt(PREF_KEY, LISTMODE_GRID_LARGE).apply(); - return true; - default: - return false; - } - } - - public void applyCurrent() { - onSharedPreferenceChanged(mPreferences, PREF_KEY); - } - - public void enable() { - mPreferences.registerOnSharedPreferenceChangeListener(this); - } - - public void disable() { - mPreferences.unregisterOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (PREF_KEY.equals(key)) { - final int mode = sharedPreferences.getInt(PREF_KEY, 0); - mListModeListener.onListModeChanged(mode != LISTMODE_LIST, mode - 1); - } - } - - public interface OnListModeListener { - void onListModeChanged(boolean grid, int sizeMode); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/MangaSaveHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/MangaSaveHelper.java deleted file mode 100644 index 0202f4dd..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/MangaSaveHelper.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.annotation.SuppressLint; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.support.annotation.StringRes; -import android.support.v7.app.AlertDialog; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.dialogs.ChaptersSelectDialog; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.services.SaveService; - -/** - * Created by admin on 21.07.17. - */ - -public class MangaSaveHelper { - - private final Context mContext; - - public MangaSaveHelper(Context context) { - this.mContext = context; - } - - public void confirmSave(MangaSummary manga) { - confirmSave(manga, R.string.chapters_to_save); - } - - public void confirmSave(MangaInfo[] mangas) { - new GetDetailsTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, - mangas); - } - - public void confirmSave(MangaSummary manga, @StringRes int dialogTitle) { - new ChaptersSelectDialog(mContext).showSave(manga, dialogTitle); - } - - public void save(MangaSummary manga) { - mContext.startService(new Intent(mContext, SaveService.class) - .putExtra("action", SaveService.ACTION_ADD) - .putExtras(manga.toBundle())); - } - - public void save(MangaSummary manga, MangaChapter chapter) { - save(manga, chapter, chapter); - } - - public void save(MangaSummary manga, MangaChapter first, MangaChapter last) { - MangaSummary copy = new MangaSummary(manga); - copy.chapters.clear(); - int firstPos = manga.chapters.indexOf(first); - int lastPos = manga.chapters.lastIndexOf(last); - copy.chapters.addAll(manga.chapters.subList(firstPos, lastPos + 1)); - save(copy); - } - - public void cancelAll() { - mContext.startService(new Intent(mContext, SaveService.class) - .putExtra("action", SaveService.ACTION_CANCEL_ALL)); - } - - public void saveLast(MangaSummary manga, int count) { - MangaSummary copy = new MangaSummary(manga); - copy.chapters.clear(); - int lastPos = manga.chapters.size() - 1; - copy.chapters.addAll(manga.chapters.subList(lastPos - count, lastPos)); - save(copy); - } - - @SuppressLint("StaticFieldLeak") - private class GetDetailsTask extends AsyncTask - implements DialogInterface.OnClickListener { - - private final ProgressDialog mProgressDialog; - - GetDetailsTask() { - super(); - mProgressDialog = new ProgressDialog(mContext); - mProgressDialog.setMessage(mContext.getString(R.string.loading)); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - mProgressDialog.setIndeterminate(true); - mProgressDialog.setCancelable(false); - mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, - mContext.getString(android.R.string.cancel), this); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mProgressDialog.show(); - } - - @Override - protected MangaSummary[] doInBackground(MangaInfo... params) { - MangaSummary[] summaries = new MangaSummary[params.length]; - MangaProvider provider; - publishProgress(0, params.length); - for (int i = 0; i < params.length && !isCancelled(); i++) { - if (params[i] == null || params[i].provider == LocalMangaProvider.class) { - summaries[i] = null; - } else try { - provider = MangaProviderManager.instanceProvider(mContext, params[i].provider); - if (provider instanceof LocalMangaProvider) { - summaries[i] = null; - } else { - summaries[i] = provider.getDetailedInfo(params[i]); - } - } catch (Exception e) { - e.printStackTrace(); - summaries[i] = null; - } - publishProgress(i, params.length); - } - return summaries; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - mProgressDialog.setIndeterminate(false); - mProgressDialog.setMax(values[1]); - mProgressDialog.setProgress(values[0]); - } - - @Override - protected void onPostExecute(final MangaSummary[] mangaSummaries) { - super.onPostExecute(mangaSummaries); - int mangas = 0, chapters = 0; - for (MangaSummary o : mangaSummaries) { - if (o != null) { - mangas++; - chapters += o.chapters.size(); - } - } - mProgressDialog.dismiss(); - new AlertDialog.Builder(mContext) - .setMessage(mContext.getString(R.string.multiple_save_confirm, mangas, chapters)) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - for (MangaSummary o : mangaSummaries) { - if (o != null) { - save(o); - } - } - } - }) - .create().show(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_NEGATIVE) { - mProgressDialog.setIndeterminate(true); - mProgressDialog.setMessage(mContext.getString(R.string.cancelling)); - this.cancel(false); - } - } - - @Override - protected void onCancelled() { - super.onCancelled(); - mProgressDialog.dismiss(); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/NotificationHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/NotificationHelper.java deleted file mode 100644 index 14235e85..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/NotificationHelper.java +++ /dev/null @@ -1,310 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.ThumbnailUtils; -import android.support.annotation.DrawableRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.v4.app.NotificationCompat; - -import com.nostra13.universalimageloader.core.ImageLoader; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.utils.ImageUtils; -import org.nv95.openmanga.utils.LayoutUtils; -import org.nv95.openmanga.utils.OneShotNotifier; -import org.nv95.openmanga.utils.WeakAsyncTask; - -import java.lang.ref.WeakReference; -import java.util.List; - -/** - * Created by nv95 on 13.02.16. - */ -@SuppressWarnings("UnusedReturnValue") -public class NotificationHelper { - - private final Context mContext; - private final OneShotNotifier mNotifier; - private final NotificationCompat.Builder mNotificationBuilder; - @Nullable - private NotificationCompat.Action mSecondaryAction = null; - @Nullable - private WeakReference mTaskRef = null; - - public NotificationHelper(Context context) { - mContext = context; - mNotifier = new OneShotNotifier(mContext); - mNotificationBuilder = new NotificationCompat.Builder(context); - mNotificationBuilder.setColor(LayoutUtils.getThemeAttrColor(context, R.attr.colorAccent)); - } - - public NotificationHelper intentActivity(Intent intent, int requestCode) { - mNotificationBuilder.setContentIntent(PendingIntent.getActivity( - mContext, - requestCode, - intent, - PendingIntent.FLAG_CANCEL_CURRENT - )); - return this; - } - - public NotificationHelper intentService(Intent intent, int requestCode) { - mNotificationBuilder.setContentIntent(PendingIntent.getService( - mContext, - requestCode, - intent, - PendingIntent.FLAG_CANCEL_CURRENT - )); - return this; - } - - public NotificationHelper intentNone() { - mNotificationBuilder.setContentIntent(null); - return this; - } - - public NotificationHelper highPriority() { - mNotificationBuilder.setPriority(NotificationCompat.PRIORITY_HIGH); - return this; - } - - public NotificationHelper lowPriority() { - mNotificationBuilder.setPriority(NotificationCompat.PRIORITY_LOW); - return this; - } - - public NotificationHelper defaultPriority() { - mNotificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT); - return this; - } - - public NotificationHelper noActions() { - mNotificationBuilder.mActions.clear(); - mSecondaryAction = null; - return this; - } - - public NotificationHelper actionCancel(PendingIntent intent) { - mNotificationBuilder.addAction(R.drawable.sym_cancel, - mContext.getString(android.R.string.cancel), - intent); - return this; - } - - public NotificationHelper actionSecondary(PendingIntent intent, @DrawableRes int icon, @StringRes int title) { - if (mSecondaryAction == null) { - mNotificationBuilder.addAction(icon, - mContext.getString(title), - intent); - mSecondaryAction = mNotificationBuilder.mActions.get(mNotificationBuilder.mActions.size() - 1); - - } else { - mSecondaryAction.actionIntent = intent; - mSecondaryAction.title = mContext.getString(title); - mSecondaryAction.icon = icon; - } - return this; - } - - public NotificationHelper title(String title) { - mNotificationBuilder.setContentTitle(title); - return this; - } - - public NotificationHelper title(@StringRes int title) { - return title(mContext.getString(title)); - } - - public NotificationHelper icon(@DrawableRes int icon) { - mNotificationBuilder.setSmallIcon(icon); - return this; - } - - public NotificationHelper image(@DrawableRes int image) { - mNotificationBuilder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), image)); - return this; - } - - public NotificationHelper text(String text) { - mNotificationBuilder.setContentText(text); - return this; - } - - public NotificationHelper text(@StringRes int text) { - return text(mContext.getString(text)); - } - - public NotificationHelper info(CharSequence info) { - mNotificationBuilder.setContentInfo(info); - return this; - } - - public NotificationHelper progress(int value, int max) { - - mNotificationBuilder.setProgress(max, value, false); - mNotificationBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS); - return this; - } - - public NotificationHelper indeterminate() { - mNotificationBuilder.setProgress(0, 0, true); - return this; - } - - public NotificationHelper noProgress() { - mNotificationBuilder.setProgress(0, 0, false); - return this; - } - - public NotificationHelper list(@StringRes int title, List items) { - NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); - style.setBigContentTitle(mContext.getString(title)); - for (String o : items) { - style.addLine(o); - } - mNotificationBuilder.setStyle(style); - return this; - } - - public NotificationHelper expandable(CharSequence bigText) { - mNotificationBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)); - return this; - } - - public NotificationHelper image(@Nullable Bitmap bitmap) { - mNotificationBuilder.setLargeIcon(bitmap); - return this; - } - - public NotificationHelper image(String path) { - WeakAsyncTask.cancel(mTaskRef, true); - Bitmap thumb = ImageUtils.getThumbnail( - path, - mContext.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), - mContext.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height) - ); - mNotificationBuilder.setLargeIcon(thumb); - if (thumb == null) { - BitmapLoadTask task = new BitmapLoadTask(this); - mTaskRef = new WeakReference<>(task); - task.start(path); - } - return this; - } - - public void update(int id) { - update(id, null); - } - - public void update(int id, @StringRes int ticker) { - update(id, mContext.getString(ticker)); - } - - public void update(int id, @Nullable String ticker) { - mNotificationBuilder.setTicker(ticker); - mNotifier.notify(id, notification()); - } - - public void notifyOnce(int id, @StringRes int ticker, int version) { - notifyOnce(id, mContext.getString(ticker), version); - } - - public void notifyOnce(int id, String ticker, int version) { - mNotificationBuilder.setTicker(ticker); - mNotifier.notifyOnce(id, notification(), version); - } - - public Notification notification() { - return mNotificationBuilder.build(); - } - - public NotificationHelper autoCancel() { - mNotificationBuilder.setAutoCancel(true); - return this; - } - - public NotificationHelper foreground(int id) { - if (mContext instanceof Service) { - foreground(id, (Service) mContext); - } - return this; - } - - public NotificationHelper foreground(int id, Service service) { - service.startForeground(id, notification()); - return this; - } - - public NotificationHelper stopForeground() { - if (mContext instanceof Service) { - stopForeground((Service) mContext); - } - return this; - } - - public NotificationHelper stopForeground(Service service) { - service.stopForeground(true); - return this; - } - - public void dismiss(int id) { - mNotifier.cancel(id); - } - - public NotificationHelper ongoing() { - mNotificationBuilder.setOngoing(true); - return this; - } - - public NotificationHelper cancelable() { - mNotificationBuilder.setOngoing(false); - return this; - } - - public NotificationHelper group(String key) { - mNotificationBuilder.setGroup(key); - return this; - } - - private static class BitmapLoadTask extends WeakAsyncTask { - - BitmapLoadTask(NotificationHelper notificationHelper) { - super(notificationHelper); - } - - @SuppressWarnings("ConstantConditions") - @Override - protected Bitmap doInBackground(String... strings) { - try { - Bitmap bitmap = ImageLoader.getInstance().loadImageSync(strings[0]); - int width = getObject().mContext.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width); - int height = getObject().mContext.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height); - if (bitmap != null) { - bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); - } - return bitmap; - } catch (Resources.NotFoundException e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(@NonNull NotificationHelper notificationHelper, Bitmap bitmap) { - super.onPostExecute(notificationHelper, bitmap); - notificationHelper.mTaskRef = null; - if (bitmap != null) { - notificationHelper.mNotificationBuilder.setLargeIcon(bitmap); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/PermissionsHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/PermissionsHelper.java deleted file mode 100644 index ee85b174..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/PermissionsHelper.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.Manifest; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.Environment; -import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; - -import org.nv95.openmanga.activities.BaseAppActivity; - -import java.io.File; - -/** - * Created by admin on 06.07.17. - */ - -public class PermissionsHelper { - - public static final int REQUEST_CODE = 10; - - public static boolean accessCommonDir(BaseAppActivity activity, String dirname) { - File dir = Environment.getExternalStoragePublicDirectory(dirname); - if (dir.canWrite()) { - return true; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - StorageManager sm = (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE); - StorageVolume volume = sm.getPrimaryStorageVolume(); - Intent intent = volume.createAccessIntent(dirname); - activity.startActivityForResult(intent, REQUEST_CODE); - return false; - } else - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && activity.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/ReaderConfig.java b/app/src/main/java/org/nv95/openmanga/helpers/ReaderConfig.java deleted file mode 100644 index 2194845c..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/ReaderConfig.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import org.nv95.openmanga.items.MangaInfo; - -/** - * Created by nv95 on 18.11.16. - */ - -@SuppressWarnings("unused") -public class ReaderConfig { - - public static final int MODE_PAGES = 0; - public static final int MODE_SCROLL = 1; - public static final int DIRECTION_DEFAULT = 0; - public static final int DIRECTION_VERTICAL = 1; - public static final int DIRECTION_REVERSED = 2; - public static final int PRELOAD_DISABLED = 0; - public static final int PRELOAD_WLAN_ONLY = 1; - public static final int PRELOAD_ALWAYS = 2; - public static final int SCALE_FIT = 0; - public static final int SCALE_FIT_W = 1; - public static final int SCALE_FIT_H = 2; - public static final int SCALE_FIT_H_REV = 5; - public static final int SCALE_ZOOM = 3; - - public final boolean keepScreenOn; - public final boolean scrollByVolumeKeys; - public final boolean adjustBrightness; - public final int brightnessValue; - public final int scrollDirection; - public final int mode; - public final int preload; - public final int scaleMode; - public final boolean tapNavs; - public final boolean hideMenuButton; - public final boolean showNumbers; - - private ReaderConfig(SharedPreferences prefs) { - keepScreenOn = prefs.getBoolean("keep_screen", true); - scrollByVolumeKeys = prefs.getBoolean("volkeyscroll", false); - adjustBrightness = prefs.getBoolean("brightness", false); - hideMenuButton = prefs.getBoolean("hide_menu", false); - brightnessValue = prefs.getInt("brightness_value", 20); - tapNavs = prefs.getBoolean("tap_navs", false); - scrollDirection = Integer.parseInt(prefs.getString("direction", "0")); - mode = Integer.parseInt(prefs.getString("r2_mode", "0")); - preload = Integer.parseInt(prefs.getString("preload", "1")); - showNumbers = prefs.getBoolean("show_numbers", true); - int scalemode = Integer.parseInt(prefs.getString("scalemode", "0")); - if (scalemode == SCALE_FIT_H && scrollDirection == DIRECTION_REVERSED) { - scalemode = SCALE_FIT_H_REV; - } - scaleMode = scalemode; - } - - public static ReaderConfig load(Context context) { - return new ReaderConfig( - PreferenceManager.getDefaultSharedPreferences(context) - ); - } - - public static ReaderConfig load(Context context, MangaInfo manga) { - return new ReaderConfig( - PreferenceManager.getDefaultSharedPreferences(context) - ); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/ScheduleHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/ScheduleHelper.java deleted file mode 100644 index 00a20771..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/ScheduleHelper.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.content.Context; -import android.content.SharedPreferences; - -/** - * Created by nv95 on 18.03.16. - */ -public class ScheduleHelper { - - public static final String ACTION_CHECK_APP_UPDATES = "app_update"; - public static final String ACTION_CHECK_NEW_CHAPTERS = "new_chapters"; - - private static final int HOUR = 1000 * 60 * 60; - private final SharedPreferences mSharedPreferences; - - public ScheduleHelper(Context context) { - mSharedPreferences = context.getSharedPreferences("schedule", Context.MODE_PRIVATE); - } - - public void actionDone(String action) { - mSharedPreferences.edit() - .putLong(action, System.currentTimeMillis()) - .apply(); - } - - public long getActionIntervalMills(String action) { - long raw = mSharedPreferences.getLong(action, -1); - if (raw != -1) { - raw = System.currentTimeMillis() - raw; - } - return raw; - } - - public int getActionIntervalHours(String action) { - long raw = getActionIntervalMills(action); - if (raw != -1) { - raw /= HOUR; - } - return (int) raw; - } - - public long getActionRawTime(String action) { - return mSharedPreferences.getLong(action, -1); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/SpeedMeasureHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/SpeedMeasureHelper.java deleted file mode 100644 index 81cb623c..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/SpeedMeasureHelper.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.nv95.openmanga.helpers; - -import java.util.Locale; -import java.util.Stack; - -/** - * Created by admin on 25.07.17. - */ - -public class SpeedMeasureHelper { - - private long mStaredAt; - private final Stack mSpeedStack; - - public SpeedMeasureHelper() { - mSpeedStack = new Stack(){ - private static final long serialVersionUID = 1L; - public Float push(Float item) { - if (this.size() == 3) { - this.removeElementAt(0); - } - return super.push(item); - } - }; - init(); - } - - public void init() { - mSpeedStack.clear(); - reset(); - } - - public void reset() { - mStaredAt = System.currentTimeMillis(); - } - - public double measure(long contentLength) { - long time = System.currentTimeMillis() - mStaredAt; - float speed = contentLength / (time / 1000f); //Bps - mSpeedStack.push(speed); - return speed; - } - - public double getAverageSpeed() { - float sum = 0; - for (Float o : mSpeedStack) { - sum += o; - } - return sum / (double)mSpeedStack.size(); - } - - public double getLastSpeed() { - return mSpeedStack.isEmpty() ? 0 : mSpeedStack.peek(); - } - - public static CharSequence formatSpeed(double kbps) { - if (kbps >= 1024) { - return String.format(Locale.getDefault(), "%.2f Mb/s", kbps / 1024D); - } - return String.format(Locale.getDefault(), "%.0f Kb/s", kbps); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/StorageHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/StorageHelper.java deleted file mode 100755 index 1b019010..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/StorageHelper.java +++ /dev/null @@ -1,294 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; -import android.support.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.nv95.openmanga.utils.FileLogger; - -import java.util.Iterator; -import java.util.concurrent.CopyOnWriteArraySet; - -/** - * Created by nv95 on 03.10.15. - */ -public class StorageHelper extends SQLiteOpenHelper { - - private static final int DB_VERSION = 19; - - public StorageHelper(Context context) { - super(context, "localmanga", null, DB_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - - db.execSQL("CREATE TABLE favourites (" - + "id INTEGER PRIMARY KEY," - + "name TEXT," - + "subtitle TEXT," - + "summary TEXT," - + "preview TEXT," - + "provider TEXT," - + "path TEXT," - + "timestamp INTEGER," - + "category INTEGER DEFAULT 0," - + "rating INTEGER DEFAULT 0" - + ");"); - - db.execSQL("CREATE TABLE history (" - + "id INTEGER PRIMARY KEY," - + "name TEXT," - + "subtitle TEXT," - + "preview TEXT," - + "summary TEXT," - + "provider TEXT," - + "path TEXT," - + "timestamp INTEGER," - + "chapter INTEGER," - + "page INTEGER," - + "size INTEGER," - + "rating INTEGER DEFAULT 0," - + "isweb INTEGER DEFAULT 0" //for reader - enable "webtoon" mode - + ");"); - - db.execSQL("CREATE TABLE search_history (" - + "_id INTEGER PRIMARY KEY," - + "query TEXT" - + ");"); - - db.execSQL("CREATE TABLE new_chapters (" - + "id INTEGER PRIMARY KEY," //0 - + "chapters_last INTEGER," //1 - кол-во глав, которые юзер видел - + "chapters INTEGER" //2 - сколько сейчас глав в манге - + ");"); - - db.execSQL("CREATE TABLE bookmarks (" - + "_id INTEGER PRIMARY KEY," - + "manga_id INTEGER," - + "page INTEGER," - + "chapter INTEGER," - + "name TEXT," - + "thumbnail TEXT," - + "timestamp INTEGER" - + ");"); - - db.execSQL("CREATE TABLE sync_delete (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "subject TEXT," - + "manga_id INTEGER," - + "timestamp INTEGER" - + ");"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - - final CopyOnWriteArraySet tables = getTableNames(db); - if(!tables.contains("new_chapters")){ - db.execSQL("CREATE TABLE new_chapters (" - + "id INTEGER PRIMARY KEY," - + "chapters_last INTEGER," - + "chapters INTEGER" - + ");"); - } - if (!tables.contains("search_history")) { - db.execSQL("CREATE TABLE search_history (" - + "_id INTEGER PRIMARY KEY," - + "query TEXT" - + ");"); - } - - if (!tables.contains("bookmarks")) { - db.execSQL("CREATE TABLE bookmarks (" - + "_id INTEGER PRIMARY KEY," - + "manga_id INTEGER," - + "page INTEGER," - + "chapter INTEGER," - + "name TEXT," - + "thumbnail TEXT," - + "timestamp INTEGER" - + ");"); - } - - if (!tables.contains("sync_delete")) { - db.execSQL("CREATE TABLE sync_delete (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "subject TEXT," - + "manga_id INTEGER," - + "timestamp INTEGER" - + ");"); - } - - // favorite - CopyOnWriteArraySet columnsFavourites = getColumsNames(db, "favourites"); - if(!columnsFavourites.contains("timestamp")) - db.execSQL("ALTER TABLE favourites ADD COLUMN timestamp INTEGER DEFAULT 0"); - if(!columnsFavourites.contains("category")) - db.execSQL("ALTER TABLE favourites ADD COLUMN category INTEGER DEFAULT 0"); - // history - columnsFavourites = getColumsNames(db, "history"); - if(!columnsFavourites.contains("summary")) - db.execSQL("ALTER TABLE history ADD COLUMN summary TEXT"); - if(!columnsFavourites.contains("rating")) - db.execSQL("ALTER TABLE history ADD COLUMN rating INTEGER DEFAULT 0"); - if(!columnsFavourites.contains("isweb")) - db.execSQL("ALTER TABLE history ADD COLUMN isweb INTEGER DEFAULT 0"); - // favourites - columnsFavourites = getColumsNames(db, "favourites"); - if(!columnsFavourites.contains("summary")) - db.execSQL("ALTER TABLE favourites ADD COLUMN summary TEXT"); - if(!columnsFavourites.contains("rating")) - db.execSQL("ALTER TABLE favourites ADD COLUMN rating INTEGER DEFAULT 0"); - } - - @Nullable - public JSONArray extractTableData(String tableName, @Nullable String where) { - JSONArray jsonArray = null; - Cursor cursor = null; - try { - jsonArray = new JSONArray(); - JSONObject jsonObject; - cursor = getReadableDatabase().query(tableName, null, where, null, null, null, null, null); - String[] columns = cursor.getColumnNames(); - if (cursor.moveToFirst()) { - do { - jsonObject = new JSONObject(); - for (int i = 0; i < columns.length; i++) { - switch (cursor.getType(i)) { - case Cursor.FIELD_TYPE_INTEGER: - jsonObject.put(columns[i], cursor.getInt(i)); - break; - case Cursor.FIELD_TYPE_STRING: - jsonObject.put(columns[i], cursor.getString(i)); - break; - case Cursor.FIELD_TYPE_FLOAT: - jsonObject.put(columns[i], cursor.getFloat(i)); - break; - case Cursor.FIELD_TYPE_BLOB: - jsonObject.put(columns[i], cursor.getBlob(i)); - break; - } - } - jsonArray.put(jsonObject); - } while (cursor.moveToNext()); - } - } catch (Exception e) { - jsonArray = null; - e.printStackTrace(); - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return jsonArray; - } - - public boolean insertTableData(String tableName, JSONArray data) { - SQLiteDatabase database = null; - boolean success = true; - try { - database = getWritableDatabase(); - database.beginTransaction(); - JSONObject o; - Object value; - ContentValues cv; - String id; - for (int i = 0; i < data.length(); i++) { - o = data.getJSONObject(i); - id = null; - cv = new ContentValues(); - Iterator iter = o.keys(); - while (iter.hasNext()) { - String key = iter.next(); - try { - value = o.get(key); - if (value instanceof Integer) { - cv.put(key, (int) value); - } else if (value instanceof String) { - cv.put(key, (String) value); - } else if (value instanceof Float) { - cv.put(key, (Float) value); - } - if ("id".equals(key)) { - id = String.valueOf(value); - } - } catch (JSONException ignored) { - } - } - if (id != null && (database.update(tableName, cv, "id=?", new String[]{id}) == 0)) { - database.insertOrThrow(tableName, null, cv); - } - } - database.setTransactionSuccessful(); - } catch (Exception e) { - success = false; - e.printStackTrace(); - FileLogger.getInstance().report("STORAGE", e); - } finally { - if (database != null) { - database.endTransaction(); - } - } - return success; - } - - public static CopyOnWriteArraySet getColumsNames(SQLiteDatabase db, String table) { - CopyOnWriteArraySet names = new CopyOnWriteArraySet<>(); - Cursor ti = db.rawQuery("PRAGMA table_info(" + table + ")", null); - if (ti.moveToFirst()) { - do { - names.add(ti.getString(1)); - } while (ti.moveToNext()); - } - ti.close(); - return names; - } - - - public static CopyOnWriteArraySet getTableNames(SQLiteDatabase db) { - CopyOnWriteArraySet result = new CopyOnWriteArraySet<>(); - try { - String s = "SELECT name FROM sqlite_master " + - "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' " + - "UNION ALL " + - "SELECT name FROM sqlite_temp_master " + - "WHERE type IN ('table','view') " + - "ORDER BY 1"; - - Cursor c = db.rawQuery(s, null); - c.moveToFirst(); - - while (c.moveToNext()) { - result.add(c.getString(c.getColumnIndex("name"))); - } - c.close(); - } catch (SQLiteException e) { - FileLogger.getInstance().report("STORAGE", e); - } - return result; - } - - public static boolean isTableExists(SQLiteDatabase database, String tableName) { - CopyOnWriteArraySet tables = getTableNames(database); - return tables.contains(tableName); - } - - public static int getRowCount(SQLiteDatabase database, String table, @Nullable String where) { - Cursor cursor = database.rawQuery("select count(*) from " - + table - + (where == null ? "" : " where " + where), null); - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - return count; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/helpers/SyncHelper.java b/app/src/main/java/org/nv95/openmanga/helpers/SyncHelper.java deleted file mode 100644 index 69a058d1..00000000 --- a/app/src/main/java/org/nv95/openmanga/helpers/SyncHelper.java +++ /dev/null @@ -1,384 +0,0 @@ -package org.nv95.openmanga.helpers; - -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import android.text.TextUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.nv95.openmanga.BuildConfig; -import org.nv95.openmanga.items.RESTResponse; -import org.nv95.openmanga.items.SyncDevice; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.services.SyncService; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.NetworkUtils; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; - -/** - * Created by admin on 10.07.17. - */ - -public class SyncHelper { - - private static WeakReference instanceRef = new WeakReference<>(null); - - public static SyncHelper get(Context context) { - SyncHelper instance = instanceRef.get(); - if (instance == null) { - instance = new SyncHelper(context); - instanceRef = new WeakReference<>(instance); - } - return instance; - } - - private String mToken; - private final Context mContext; - private final SharedPreferences mPreferences; - - private SyncHelper(Context context) { - mContext = context; - mPreferences = PreferenceManager.getDefaultSharedPreferences(context); - mToken = mPreferences.getString("sync.token", null); - } - - public boolean isHistorySyncEnabled() { - return mPreferences.getBoolean("sync.history", true); - } - - public boolean isFavouritesSyncEnabled() { - return mPreferences.getBoolean("sync.favourites", true); - } - - public long getLastHistorySync() { - return mPreferences.getLong("sync.last_history", 0); - } - - public long getLastFavouritesSync() { - return mPreferences.getLong("sync.last_favourites", 0); - } - - public void setHistorySynced() { - mPreferences.edit() - .putLong("sync.last_history", System.currentTimeMillis()) - .apply(); - } - - public void setFavouritesSynced() { - mPreferences.edit() - .putLong("sync.last_favourites", System.currentTimeMillis()) - .apply(); - } - - private void setToken(String token) { - setToken(token, null); - } - - private void setToken(String token, String username) { - mToken = token; - SharedPreferences.Editor editor = mPreferences.edit(); - if (token != null) { - editor.putString("sync.token", token).remove("sync.last_favourites").remove("sync.last_history"); - if (username != null) { - editor.putString("sync.username", username); - } - } else { - editor.remove("sync.token").remove("sync.username").remove("sync.last_favourites").remove("sync.last_history"); - } - editor.apply(); - } - - @WorkerThread - public RESTResponse register(String login, String password) { - RESTResponse response = NetworkUtils.restQuery( - BuildConfig.SYNC_URL + "/user", - null, - NetworkUtils.HTTP_PUT, - "login", login, - "password", password, - "device", - AppHelper.getDeviceSummary() - ); - if (response.isSuccess()) { - try { - setToken(response.getData().getString("token"), login); - } catch (JSONException e) { - e.printStackTrace(); - return RESTResponse.fromThrowable(e); - } - } - return response; - } - - @WorkerThread - public RESTResponse authorize(String login, String password) { - RESTResponse response = NetworkUtils.restQuery( - BuildConfig.SYNC_URL + "/user", - null, - NetworkUtils.HTTP_POST, - "login", login, - "password", password, - "device", - AppHelper.getDeviceSummary() - ); - if (response.isSuccess()) { - try { - setToken(response.getData().getString("token"), login); - } catch (JSONException e) { - e.printStackTrace(); - return RESTResponse.fromThrowable(e); - } - } - return response; - } - - public boolean isAuthorized() { - return !TextUtils.isEmpty(mToken); - } - - @WorkerThread - public RESTResponse syncHistory() { - try { - HistoryProvider provider = HistoryProvider.getInstance(mContext); - long lastSync = getLastHistorySync(); - JSONArray updated = provider.dumps(lastSync); - if (updated == null) { - return RESTResponse.fromThrowable(new NullPointerException()); - } - JSONArray deleted = dumpsDeleted("history"); - if (deleted == null) { - return RESTResponse.fromThrowable(new NullPointerException("deleted is null")); - } - RESTResponse resp = NetworkUtils.restQuery( - BuildConfig.SYNC_URL + "/history", - mToken, - NetworkUtils.HTTP_POST, - "timestamp", - String.valueOf(lastSync), - "updated", - updated.toString(), - "deleted", - deleted.toString() - ); - if (!resp.isSuccess()) { - if (resp.getResponseCode() == RESTResponse.RC_INVALID_TOKEN) { - setToken(null); - } - return resp; - } - if (!provider.inject(resp.getData().getJSONArray("updated"))) { - return RESTResponse.fromThrowable(new SQLException("Cannot write data")); - } - deleted = resp.getData().getJSONArray("deleted"); - for (int i=0;i getUserDevices(boolean includeSelf) throws Exception { - ArrayList list = new ArrayList<>(); - RESTResponse resp = NetworkUtils.restQuery( - BuildConfig.SYNC_URL + "/user", - mToken, - NetworkUtils.HTTP_GET, - "self", - includeSelf ? "1" : "0" - ); - if (!resp.isSuccess()) { - if (resp.getResponseCode() == RESTResponse.RC_INVALID_TOKEN) { - setToken(null); - noauthBroadcast(); - } - return null; - } - JSONArray devices = resp.getData().getJSONArray("devices"); - int len = devices.length(); - for (int i = 0; i < len; i++) { - JSONObject o = devices.getJSONObject(i); - list.add(new SyncDevice( - o.getInt("id"), - o.getString("device"), - o.getLong("created_at") - )); - } - return list; - } - - public RESTResponse detachDevice(int id) { - return NetworkUtils.restQuery( - BuildConfig.SYNC_URL + "/user", - mToken, - NetworkUtils.HTTP_DELETE, - "id", - String.valueOf(id) - ); - } - - public RESTResponse logout() { - RESTResponse resp = NetworkUtils.restQuery( - BuildConfig.SYNC_URL + "/user", - mToken, - NetworkUtils.HTTP_DELETE, - "self", - "1" - ); - if (resp.isSuccess()) { - setToken(null); - } - return resp; - } - - private void noauthBroadcast() { - Intent intent = new Intent(); - intent.setAction(SyncService.SYNC_EVENT); - intent.putExtra("what", SyncService.MSG_UNAUTHORIZED); - mContext.sendBroadcast(intent); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/Bookmark.java b/app/src/main/java/org/nv95/openmanga/items/Bookmark.java deleted file mode 100644 index 19eb7b9a..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/Bookmark.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.nv95.openmanga.items; - -import android.content.ContentValues; - -/** - * Created by nv95 on 20.11.16. - */ - -public class Bookmark { - - public int mangaId; - public int chapter; - public int page; - public String name; - public String thumbnailFile; - public long datetime; - - private int id = 0; - - @Override - public int hashCode() { - if (id == 0) { - id = (int) (datetime - 20000); - } - return id; - } - - public Bookmark() { - } - - public Bookmark(int id) { - this.id = id; - } - - public ContentValues toContentValues() { - ContentValues cv = new ContentValues(); - cv.put("_id", hashCode()); - cv.put("manga_id", mangaId); - cv.put("chapter", chapter); - cv.put("page", page); - cv.put("name", name); - cv.put("thumbnail", thumbnailFile); - cv.put("timestamp", datetime); - return cv; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/DownloadInfo.java b/app/src/main/java/org/nv95/openmanga/items/DownloadInfo.java deleted file mode 100644 index 9ab523a4..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/DownloadInfo.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.nv95.openmanga.items; - -import org.nv95.openmanga.lists.ChaptersList; - -import java.util.Arrays; - -/** - * Created by nv95 on 12.03.16. - */ -public class DownloadInfo extends MangaInfo { - - public String description; - public final int max; - public int pos; - public int state; - public final ChaptersList chapters; - public final int[] chaptersProgresses; - public final int[] chaptersSizes; - - public DownloadInfo(MangaSummary mangaSummary) { - this.id = mangaSummary.hashCode(); - this.name = mangaSummary.name; - this.genres = mangaSummary.genres; - this.path = mangaSummary.path; - this.preview = mangaSummary.preview; - this.subtitle = mangaSummary.subtitle; - this.provider = mangaSummary.provider; - this.rating = mangaSummary.rating; - this.description = mangaSummary.description; - this.chapters = mangaSummary.chapters; - this.max = chapters.size(); - chaptersProgresses = new int[max]; - Arrays.fill(chaptersProgresses, 0); //надо ли? - chaptersSizes = new int[max]; - Arrays.fill(chaptersSizes, 0); - } - - public int getChapterProgressPercent() { - if (pos >= max) { - return 100; - } else if (chaptersSizes[pos] == 0) { - return 0; - } else { - return chaptersProgresses[pos] * 100 / chaptersSizes[pos]; - } - } - - @Override - public boolean equals(Object o) { - return o instanceof DownloadInfo && - ((DownloadInfo)o).id == id; - } - - @Override - public int hashCode() { - return id == 0 ? path.hashCode() : id; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/HistoryMangaInfo.java b/app/src/main/java/org/nv95/openmanga/items/HistoryMangaInfo.java deleted file mode 100644 index 6f237bb2..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/HistoryMangaInfo.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.nv95.openmanga.items; - -/** - * Created by admin on 19.07.17. - */ - -public class HistoryMangaInfo extends MangaInfo { - - public long timestamp; - public int chapter; - public int page; -} diff --git a/app/src/main/java/org/nv95/openmanga/items/LocalMangaInfo.java b/app/src/main/java/org/nv95/openmanga/items/LocalMangaInfo.java deleted file mode 100644 index 4d029594..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/LocalMangaInfo.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.nv95.openmanga.items; - -/** - * Created by nv95 on 02.07.16. - */ - -public class LocalMangaInfo { - - public long id; - public String name; - public String path; - public long size; -} diff --git a/app/src/main/java/org/nv95/openmanga/items/MangaChapter.java b/app/src/main/java/org/nv95/openmanga/items/MangaChapter.java deleted file mode 100755 index 8f4a06bd..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/MangaChapter.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.nv95.openmanga.items; - -import android.os.Bundle; - -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; - -/** - * Created by nv95 on 02.10.15. - */ -public class MangaChapter { - - public int id; - public String name; - public int number; - public String readLink; - public Class provider; - - public MangaChapter() { - number = -1; - } - - public MangaChapter(Bundle bundle) { - id = bundle.getInt("id"); - name = bundle.getString("name"); - readLink = bundle.getString("readLink"); - number = bundle.getInt("number"); - try { - provider = (Class) Class.forName(bundle.getString("provider")); - } catch (ClassNotFoundException e) { - provider = LocalMangaProvider.class; - } - } - - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt("id", id()); - bundle.putString("name", name); - bundle.putString("readLink", readLink); - bundle.putInt("number", number); - bundle.putString("provider", provider.getName()); - return bundle; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MangaChapter chapter = (MangaChapter) o; - - return readLink != null ? readLink.equals(chapter.readLink) : chapter.readLink == null; - } - - public int id() { - if (id == 0) { - id = hashCode(); - } - return id; - } - - @Override - public int hashCode() { - return readLink != null ? readLink.hashCode() : 0; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/MangaInfo.java b/app/src/main/java/org/nv95/openmanga/items/MangaInfo.java deleted file mode 100755 index 4996e7c6..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/MangaInfo.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.nv95.openmanga.items; - -import android.os.Bundle; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; - -/** - * Created by nv95 on 30.09.15. - */ -public class MangaInfo { - - public static final int STATUS_UNKNOWN = 0; - public static final int STATUS_COMPLETED = 1; - public static final int STATUS_ONGOING = 2; - - public int id; - public String name; - public String subtitle; - public String genres; - public String path; - public String preview; - public Class provider; - public int status; - @Nullable - public String extra; - public byte rating; //0..100 - - public MangaInfo(String name, String genres, String path, String preview) { - this.name = name; - this.genres = genres; - this.path = path; - this.preview = preview; - this.status = STATUS_UNKNOWN; - this.extra = null; - } - - public MangaInfo(Bundle bundle) { - id = bundle.getInt("id"); - name = bundle.getString("name"); - genres = bundle.getString("genres"); - path = bundle.getString("path"); - preview = bundle.getString("preview"); - subtitle = bundle.getString("subtitle"); - try { - provider = (Class) Class.forName(bundle.getString("provider")); - } catch (ClassNotFoundException e) { - provider = LocalMangaProvider.class; - } - status = bundle.getInt("status", 0); - extra = bundle.getString("extra"); - rating = bundle.getByte("rating"); - } - - public MangaInfo() { - this.status = STATUS_UNKNOWN; - this.extra = null; - } - - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt("id", id); - bundle.putString("name", name); - bundle.putString("genres", genres); - bundle.putString("path", path); - bundle.putString("preview", preview); - bundle.putString("subtitle", subtitle); - bundle.putString("provider", provider.getName()); - bundle.putInt("status", status); - bundle.putString("extra", extra); - bundle.putByte("rating", rating); - return bundle; - } - - public boolean isCompleted() { - return status == STATUS_COMPLETED; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MangaInfo mangaInfo = (MangaInfo) o; - return !(path != null ? !path.equals(mangaInfo.path) : mangaInfo.path != null); - - } - - @Override - public int hashCode() { - return id == 0 ? id : (path != null ? path.hashCode() : 0); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/MangaPage.java b/app/src/main/java/org/nv95/openmanga/items/MangaPage.java deleted file mode 100755 index 085727c5..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/MangaPage.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.nv95.openmanga.items; - -import org.nv95.openmanga.providers.MangaProvider; - -/** - * Created by nv95 on 30.09.15. - */ -public class MangaPage { - - public int id; - public String path; - public Class provider; - - public MangaPage() { - } - - public MangaPage(String path) { - this.path = path; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/MangaSummary.java b/app/src/main/java/org/nv95/openmanga/items/MangaSummary.java deleted file mode 100755 index 47ef1222..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/MangaSummary.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.nv95.openmanga.items; - -import android.os.Bundle; -import android.support.annotation.NonNull; - -import org.nv95.openmanga.lists.ChaptersList; - -/** - * Created by nv95 on 30.09.15. - * Более подробная информация - */ -public class MangaSummary extends MangaInfo { - - public String description; - @NonNull - public ChaptersList chapters; - - public MangaSummary(MangaInfo mangaInfo) { - id = mangaInfo.id; - this.name = mangaInfo.name; - this.genres = mangaInfo.genres; - this.path = mangaInfo.path; - this.preview = mangaInfo.preview; - this.subtitle = mangaInfo.subtitle; - this.provider = mangaInfo.provider; - this.status = mangaInfo.status; - this.extra = mangaInfo.extra; - this.rating = mangaInfo.rating; - this.description = ""; - this.chapters = new ChaptersList(); - } - - public MangaSummary(MangaSummary mangaSummary) { - id = mangaSummary.id; - this.name = mangaSummary.name; - this.genres = mangaSummary.genres; - this.path = mangaSummary.path; - this.preview = mangaSummary.preview; - this.subtitle = mangaSummary.subtitle; - this.provider = mangaSummary.provider; - this.description = mangaSummary.description; - this.status = mangaSummary.status; - this.extra = mangaSummary.extra; - this.rating = mangaSummary.rating; - this.chapters = new ChaptersList(mangaSummary.chapters); - } - - public MangaSummary(Bundle bundle) { - super(bundle); - this.description = bundle.getString("description"); - chapters = new ChaptersList(bundle); - } - - @Override - public Bundle toBundle() { - Bundle bundle = super.toBundle(); - bundle.putString("description", description); - bundle.putAll(chapters.toBundle()); - return bundle; - } - - public String getDescription() { - return description; - } - - @NonNull - public ChaptersList getChapters() { - return chapters; - } - -} diff --git a/app/src/main/java/org/nv95/openmanga/items/MangaUpdateInfo.java b/app/src/main/java/org/nv95/openmanga/items/MangaUpdateInfo.java deleted file mode 100644 index d612d2a5..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/MangaUpdateInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.nv95.openmanga.items; - -/** - * Created by nv95 on 18.03.16. - */ -public class MangaUpdateInfo { - - public int mangaId; - public String mangaName; - - public int lastChapters; - public int chapters; - - public MangaUpdateInfo() { - } - - public MangaUpdateInfo(int mangaId) { - this.mangaId = mangaId; - } - - public int getNewChapters() { - return lastChapters > 0 ? chapters - lastChapters : 0; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/RESTResponse.java b/app/src/main/java/org/nv95/openmanga/items/RESTResponse.java deleted file mode 100644 index 7fab594d..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/RESTResponse.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.nv95.openmanga.items; - -import android.support.annotation.Nullable; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Created by admin on 11.07.17. - */ - -public class RESTResponse { - - public static final int RC_OK = 200; - public static final int RC_SERVER_ERROR = 500; - public static final int RC_CLIENT_ERROR = 0; - public static final int RC_INVALID_TOKEN = 403; - - private String state; - @Nullable - private - String message; - private JSONObject data; - private int responseCode; - - private RESTResponse() { - } - - public RESTResponse(JSONObject data) { - this(data, 200); - } - - public RESTResponse(JSONObject data, int responseCode) { - this.responseCode = responseCode; - this.data = data; - try { - this.state = data.getString("state"); - this.message = data.has("message") ? data.getString("message") : null; - } catch (JSONException e) { - e.printStackTrace(); - this.state = "fail"; - this.message = e.getMessage(); - } - } - - public int getResponseCode() { - return responseCode; - } - - public boolean isSuccess() { - return "success".equals(state); - } - - public String getMessage() { - return message == null ? "Internal error" : message; - } - - public JSONObject getData() { - return data; - } - - public static RESTResponse fromThrowable(Throwable e) { - RESTResponse resp = new RESTResponse(); - resp.state = "fail"; - resp.message = e.getMessage(); - resp.data = new JSONObject(); - resp.responseCode = 0; - return resp; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/items/SimpleDownload.java b/app/src/main/java/org/nv95/openmanga/items/SimpleDownload.java deleted file mode 100644 index 33901eba..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/SimpleDownload.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.nv95.openmanga.items; - -import android.support.annotation.Nullable; - -import org.nv95.openmanga.helpers.SpeedMeasureHelper; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.utils.NoSSLv3SocketFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; - -import javax.net.ssl.HttpsURLConnection; - -import info.guardianproject.netcipher.NetCipher; - -/** - * Created by nv95 on 12.02.16. - */ -public class SimpleDownload implements Runnable { - - private final String mSourceUrl; - private final File mDestination; - @Nullable - private SpeedMeasureHelper mSpeedMeasureHelper; - - public SimpleDownload(String sourceUrl, File destination) { - this.mSourceUrl = sourceUrl; - this.mDestination = destination; - } - - public SimpleDownload setSpeedMeasureHelper(SpeedMeasureHelper helper) { - mSpeedMeasureHelper = helper; - return this; - } - - @Override - public void run() { - InputStream input = null; - OutputStream output = null; - HttpURLConnection connection = null; - try { - connection = NetCipher.getHttpURLConnection(mSourceUrl); - if (connection instanceof HttpsURLConnection) { - ((HttpsURLConnection) connection).setSSLSocketFactory(NoSSLv3SocketFactory.getInstance()); - } - MangaProviderManager.prepareConnection(connection); - connection.setConnectTimeout(15000); - connection.setReadTimeout(15000); - connection.connect(); - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - return; - } - input = connection.getInputStream(); - output = new FileOutputStream(mDestination); - byte data[] = new byte[4096]; - int count; - long total = 0; - if (mSpeedMeasureHelper != null) { - mSpeedMeasureHelper.reset(); - } - while ((count = input.read(data)) != -1) { - total += count; - output.write(data, 0, count); - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - } - if (mSpeedMeasureHelper != null && total > 0) { - mSpeedMeasureHelper.measure(total); - } - } catch (Exception e) { - if (mDestination.exists()) { - mDestination.delete(); - } - } finally { - try { - if (output != null) - output.close(); - if (input != null) - input.close(); - } catch (IOException ignored) { - } - if (connection != null) - connection.disconnect(); - } - } - - public boolean isSuccess() { - return mDestination.exists(); - } - - public File getDestination() { - return mDestination; - } - - public String getSourceUrl() { - return mSourceUrl; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/SyncDevice.java b/app/src/main/java/org/nv95/openmanga/items/SyncDevice.java deleted file mode 100644 index 537adf14..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/SyncDevice.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.nv95.openmanga.items; - -/** - * Created by admin on 13.07.17. - */ - -public class SyncDevice { - public int id; - public String name; - public long created_at; - - public SyncDevice(int id, String name, long created_at) { - this.id = id; - this.name = name; - this.created_at = created_at; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/items/ThumbSize.java b/app/src/main/java/org/nv95/openmanga/items/ThumbSize.java deleted file mode 100644 index cd05bbfb..00000000 --- a/app/src/main/java/org/nv95/openmanga/items/ThumbSize.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.nv95.openmanga.items; - -import android.view.View; - -import com.nostra13.universalimageloader.core.assist.ImageSize; - -/** - * Created by nv95 on 09.02.16. - */ -public class ThumbSize { - - public static ThumbSize THUMB_SIZE_LIST; - public static ThumbSize THUMB_SIZE_SMALL; - public static ThumbSize THUMB_SIZE_MEDIUM; - public static ThumbSize THUMB_SIZE_LARGE; - - private int mWidth; - private int mHeight; - - public ThumbSize(int width, int height) { - mHeight = height; - mWidth = width; - } - - public ThumbSize(int width, float aspectRatio) { - mWidth = width; - mHeight = Math.round(width * aspectRatio); - } - - public ThumbSize(View view) { - mHeight = view.getMeasuredHeight(); - mWidth = view.getMeasuredWidth(); - } - - public int getWidth() { - return mWidth; - } - - public int getHeight() { - return mHeight; - } - - @Override - public String toString() { - return "_" + mHeight + "x" + mWidth; - } - - @Override - public boolean equals(Object o) { - return o instanceof ThumbSize && - ((ThumbSize)o).mHeight == mHeight && - ((ThumbSize)o).mWidth == mWidth; - } - - public ImageSize toImageSize() { - return new ImageSize(mWidth, mHeight); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/lists/ChaptersList.java b/app/src/main/java/org/nv95/openmanga/lists/ChaptersList.java deleted file mode 100755 index bdcc06b2..00000000 --- a/app/src/main/java/org/nv95/openmanga/lists/ChaptersList.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.nv95.openmanga.lists; - -import android.os.Bundle; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.items.MangaChapter; - -import java.util.ArrayList; - -/** - * Created by nv95 on 02.10.15. - */ -public class ChaptersList extends ArrayList { - - public ChaptersList() { - } - - public ChaptersList(Bundle bundle) { - int n = bundle.getInt("size"); - for (int i = 0; i < n; i++) { - add(new MangaChapter(bundle.getBundle("chapter" + i))); - } - } - - public ChaptersList(ChaptersList chapters) { - super(chapters); - } - - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt("size", size()); - for (int i = 0; i < size(); i++) { - bundle.putBundle("chapter" + i, get(i).toBundle()); - } - return bundle; - } - - public String[] getNames() { - String[] res = new String[size()]; - for (int i = 0; i < size(); i++) { - res[i] = get(i).name; - } - return res; - } - - public ChaptersList complementByName(ChaptersList list) { - ChaptersList res = new ChaptersList(); - boolean exists; - for (MangaChapter o:this) { - exists = false; - for (MangaChapter o1:list) { - if (o.name != null && o.name.equals(o1.name)) { - exists = true; - break; - } - } - if (!exists) { - res.add(o); - } - } - return res; - } - - @Nullable - public MangaChapter getByNumber(int number) { - for (MangaChapter o : this) { - if (o != null && o.number == number) { - return o; - } - } - return null; - } - - public int indexByNumber(int number) { - MangaChapter o; - for (int i=0;i maxNumber) { - maxNumber = o.number; - res = o; - } - } - return res; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/lists/Genres.java b/app/src/main/java/org/nv95/openmanga/lists/Genres.java deleted file mode 100644 index e29d4833..00000000 --- a/app/src/main/java/org/nv95/openmanga/lists/Genres.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.nv95.openmanga.lists; - -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.text.TextUtils; - -/** - * Created by nv95 on 22.06.16. - */ - -public class Genres implements Parcelable { - - private final String[] mData; - - public Genres(@NonNull String data) { - mData = data.split("[,]?\\s"); - } - - private Genres(Parcel in) { - mData = in.createStringArray(); - } - - public static final Creator CREATOR = new Creator() { - @Override - public Genres createFromParcel(Parcel in) { - return new Genres(in); - } - - @Override - public Genres[] newArray(int size) { - return new Genres[size]; - } - }; - - @Override - public String toString() { - return TextUtils.join(" ", mData); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStringArray(mData); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/lists/MangaList.java b/app/src/main/java/org/nv95/openmanga/lists/MangaList.java deleted file mode 100755 index 31c83afe..00000000 --- a/app/src/main/java/org/nv95/openmanga/lists/MangaList.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.nv95.openmanga.lists; - -import android.support.annotation.Nullable; - -import org.nv95.openmanga.items.MangaInfo; - -/** - * Created by nv95 on 30.09.15. - */ -public class MangaList extends PagedList { - - public static MangaList empty() { - return new MangaList(); - } - - public MangaList first(int size) { - final MangaList result = new MangaList(); - for (int i=0;i= 0 && pos < size(); - } - - @Nullable - public MangaInfo getById(int id) { - for (MangaInfo o : this) { - if (o.id == id) { - return o; - } - } - return null; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/lists/PagedList.java b/app/src/main/java/org/nv95/openmanga/lists/PagedList.java deleted file mode 100644 index df30dfdd..00000000 --- a/app/src/main/java/org/nv95/openmanga/lists/PagedList.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.nv95.openmanga.lists; - -import java.util.ArrayList; - -/** - * Created by nv95 on 25.01.16. - */ -public class PagedList extends ArrayList { - - private int mPages = 0; - private boolean mHasNext; - - public void appendPage(PagedList list) { - mPages++; - this.addAll(list); - } - - @Override - public void clear() { - super.clear(); - mPages = 0; - } - - public int getPagesCount() { - return mPages; - } - - public boolean isHasNext() { - return mHasNext; - } - - public void setHasNext(boolean hasNext) { - this.mHasNext = hasNext; - } - - public void setPagesCount(int count) { - mPages = Math.max(count, 0); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/FilterCallback.java b/app/src/main/java/org/nv95/openmanga/mangalist/FilterCallback.java new file mode 100644 index 00000000..7546832a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/FilterCallback.java @@ -0,0 +1,12 @@ +package org.nv95.openmanga.mangalist; + +import org.nv95.openmanga.core.models.MangaGenre; + +/** + * Created by koitharu on 31.12.17. + */ + +interface FilterCallback { + + void setFilter(int sort, MangaGenre[] genres); +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/FilterDialogFragment.java b/app/src/main/java/org/nv95/openmanga/mangalist/FilterDialogFragment.java new file mode 100644 index 00000000..7545e063 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/FilterDialogFragment.java @@ -0,0 +1,112 @@ +package org.nv95.openmanga.mangalist; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.Toast; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.dialogs.AppBaseBottomSheetDialogFragment; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.views.recyclerview.HeaderDividerItemDecoration; +import org.nv95.openmanga.core.models.MangaGenre; + +import java.util.ArrayList; + +/** + * Created by koitharu on 31.12.17. + */ + +public final class FilterDialogFragment extends AppBaseBottomSheetDialogFragment implements View.OnClickListener { + + private RecyclerView mRecyclerView; + private Toolbar mToolbar; + private AppBarLayout mAppBar; + private FilterSortAdapter mAdapter; + + private int[] mSorts; + private MangaGenre[] mGenres; + private MangaQueryArguments mQueryArgs; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Bundle args = getArguments(); + assert args != null; + mSorts = args.getIntArray("sorts"); + mGenres = (MangaGenre[]) args.getParcelableArray("genres"); + mQueryArgs = MangaQueryArguments.from(args.getBundle("query")); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.dialog_filter, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = view.findViewById(R.id.recyclerView); + mToolbar = view.findViewById(R.id.toolbar); + mAppBar = view.findViewById(R.id.appbar); + Button mButtonApply = view.findViewById(R.id.buttonApply); + Button mButtonReset = view.findViewById(R.id.buttonReset); + + mButtonApply.setOnClickListener(this); + mButtonReset.setOnClickListener(this); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Activity activity = getActivity(); + assert activity != null; + mAdapter = new FilterSortAdapter(activity, mSorts, mGenres, mQueryArgs.sort, mQueryArgs.genresValues()); + mRecyclerView.setAdapter(mAdapter); + mRecyclerView.addItemDecoration(new HeaderDividerItemDecoration(activity)); + mToolbar.setNavigationOnClickListener(this); + + /*final BottomSheetBehavior behavior = BottomSheetBehavior.from(getDialog().findViewById(android.support.design.R.id.design_bottom_sheet)); + behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + AnimationUtils.setVisibility(mAppBar, newState == BottomSheetBehavior.STATE_EXPANDED ? View.VISIBLE : View.GONE); + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + + } + });*/ + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.buttonReset: + mAdapter.reset(); + final Toast toast = Toast.makeText(v.getContext(), R.string.filter_reset, Toast.LENGTH_SHORT); + toast.setGravity(Gravity.TOP, 0, 0); + toast.show(); + break; + case R.id.buttonApply: + Activity activity = getActivity(); + if (activity != null && activity instanceof FilterCallback) { + ArrayList genres = CollectionsUtils.getIfTrue(mGenres, mAdapter.getSelectedGenres()); + ((FilterCallback) activity).setFilter(mAdapter.getSelectedSort(), genres.toArray(new MangaGenre[genres.size()])); + } + default: + dismiss(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/FilterSortAdapter.java b/app/src/main/java/org/nv95/openmanga/mangalist/FilterSortAdapter.java new file mode 100644 index 00000000..a8738e09 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/FilterSortAdapter.java @@ -0,0 +1,175 @@ +package org.nv95.openmanga.mangalist; + +import android.content.Context; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v7.widget.RecyclerView; +import android.util.SparseBooleanArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckedTextView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.TypedString; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Created by koitharu on 31.12.17. + */ + +public final class FilterSortAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private int mSelectedSort; + private final SparseBooleanArray mSelectedGenres; + + FilterSortAdapter(Context context, @NonNull @StringRes int[] sorts, @NonNull MangaGenre[] genres, int selectedSort, String[] selectedGenres) { + mDataset = new ArrayList<>(); + mSelectedGenres = new SparseBooleanArray(); + setHasStableIds(true); + if (sorts.length != 0) { + mDataset.add(new TypedString(context, R.string.action_sort, ItemViewType.TYPE_ITEM_HEADER, -1)); + for (int i = 0; i < sorts.length; i++) { + mDataset.add(new TypedString(context, sorts[i], ItemViewType.TYPE_ITEM_SORT, i)); + } + } + mSelectedSort = selectedSort; + if (genres.length != 0) { + mDataset.add(new TypedString(context, R.string.genres, ItemViewType.TYPE_ITEM_HEADER, -1)); + for (int i = 0; i < genres.length; i++) { + mDataset.add(new TypedString(context, genres[i].nameId, ItemViewType.TYPE_ITEM_GENRE, i)); + if (CollectionsUtils.contains(selectedGenres, genres[i].value)) { + mSelectedGenres.put(i, true); + } + } + } + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, @ItemViewType int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + RecyclerView.ViewHolder holder; + switch (viewType) { + case ItemViewType.TYPE_ITEM_GENRE: + holder = new GenreHolder(inflater.inflate(R.layout.item_checkable_check, parent, false)); + break; + case ItemViewType.TYPE_ITEM_HEADER: + return new HeaderHolder(inflater.inflate(R.layout.header_group, parent, false)); + case ItemViewType.TYPE_ITEM_SORT: + holder = new SortHolder(inflater.inflate(R.layout.item_checkable_radio, parent, false)); + break; + default: + throw new AssertionError(""); + } + return holder; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + TypedString item = mDataset.get(position); + if (holder instanceof HeaderHolder) { + ((HeaderHolder) holder).textView.setText(item.toString()); + } else if (holder instanceof GenreHolder) { + ((GenreHolder) holder).checkedTextView.setText(item.toString()); + if (holder instanceof SortHolder) { + ((SortHolder) holder).checkedTextView.setChecked(item.getSubPosition() == mSelectedSort); + } else { + ((GenreHolder) holder).checkedTextView.setChecked(mSelectedGenres.get(item.getSubPosition(), false)); + } + } + + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).hashCode(); + } + + @ItemViewType + @Override + public int getItemViewType(int position) { + return mDataset.get(position).getType(); + } + + int getSelectedSort() { + return mSelectedSort; + } + + SparseBooleanArray getSelectedGenres() { + return mSelectedGenres; + } + + void reset() { + mSelectedSort = 0; + mSelectedGenres.clear(); + notifyDataSetChanged(); + } + + class GenreHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final CheckedTextView checkedTextView; + + GenreHolder(View itemView) { + super(itemView); + itemView.setOnClickListener(this); + checkedTextView = itemView.findViewById(R.id.checkedTextView); + itemView.setTag(1); //some magic + } + + @Override + public void onClick(View view) { + int pos = mDataset.get(getAdapterPosition()).getSubPosition(); + if (mSelectedGenres.get(pos, false)) { + mSelectedGenres.put(pos, false); + } else { + mSelectedGenres.put(pos, true); + } + notifyDataSetChanged(); + } + } + + final class SortHolder extends GenreHolder { + + SortHolder(View itemView) { + super(itemView); + } + + @Override + public void onClick(View view) { + mSelectedSort = mDataset.get(getAdapterPosition()).getSubPosition(); + notifyDataSetChanged(); + } + } + + final class HeaderHolder extends RecyclerView.ViewHolder { + + final TextView textView; + + HeaderHolder(View itemView) { + super(itemView); + textView = itemView.findViewById(R.id.textView); + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ItemViewType.TYPE_ITEM_HEADER, ItemViewType.TYPE_ITEM_SORT, ItemViewType.TYPE_ITEM_GENRE}) + public @interface ItemViewType { + int TYPE_ITEM_HEADER = 0; + int TYPE_ITEM_SORT = 1; + int TYPE_ITEM_GENRE = 2; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/MangaListActivity.java b/app/src/main/java/org/nv95/openmanga/mangalist/MangaListActivity.java new file mode 100644 index 00000000..7828fb5c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/MangaListActivity.java @@ -0,0 +1,267 @@ +package org.nv95.openmanga.mangalist; + +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewStub; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.common.views.EndlessRecyclerView; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaGenre; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.storage.FlagsStorage; +import org.nv95.openmanga.tools.settings.AuthorizationDialog; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by koitharu on 28.12.17. + */ + +public final class MangaListActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks>, + View.OnClickListener, FilterCallback, SearchView.OnQueryTextListener, EndlessRecyclerView.OnLoadMoreListener, + AuthorizationDialog.Callback { + + private EndlessRecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private MenuItem mMenuItemSearch; + private TextView mTextViewError; + private View mErrorView; + + private List mDataset = new ArrayList<>(); + private MangaListAdapter mAdapter; + private MangaProvider mProvider; + private MangaQueryArguments mArguments = new MangaQueryArguments(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_mangalist); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mProgressBar = findViewById(R.id.progressBar); + mRecyclerView = findViewById(R.id.recyclerView); + FloatingActionButton mFabFilter = findViewById(R.id.fabFilter); + mErrorView = findViewById(R.id.stub_error); + + mAdapter = new MangaListAdapter(mDataset, FlagsStorage.get(this).isListDetailed()); + mFabFilter.setOnClickListener(this); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); + mRecyclerView.setOnLoadMoreListener(this); + + final String cname = getIntent().getStringExtra("provider.cname"); + assert cname != null; + mProvider = MangaProvider.get(this, cname); + setTitle(mProvider.getName()); + if (mProvider.getAvailableGenres().length == 0 && mProvider.getAvailableSortOrders().length == 0) { + mFabFilter.hide(); + } + + mRecyclerView.setAdapter(mAdapter); + + load(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_mangalist, menu); + mMenuItemSearch = menu.findItem(R.id.action_search); + SearchView mSearchView = (SearchView) mMenuItemSearch.getActionView(); + mSearchView.setOnQueryTextListener(this); + mSearchView.setOnSearchClickListener(v -> mSearchView.setQuery(mArguments.query, false)); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.action_authorize).setVisible(mProvider.isAuthorizationSupported() && !mProvider.isAuthorized()); + menu.findItem(R.id.option_details).setChecked(mAdapter.isDetailed()); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_authorize: + final AuthorizationDialog dialog = new AuthorizationDialog(); + final Bundle args = new Bundle(1); + args.putString("provider", mProvider.getCName()); + dialog.setArguments(args); + dialog.show(getSupportFragmentManager(), "auth"); + return true; + case R.id.option_details: + boolean checked = !item.isChecked(); + item.setChecked(checked); + FlagsStorage.get(this).setIsListDetailed(checked); + mAdapter.setIsDetailed(checked); + LayoutUtils.forceUpdate(mRecyclerView); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public Loader> onCreateLoader(int i, Bundle bundle) { + final MangaQueryArguments queryArgs = MangaQueryArguments.from(bundle); + + setSubtitle(!TextUtils.isEmpty(queryArgs.query) + ? queryArgs.query + : queryArgs.genres.length != 0 + ? MangaGenre.joinNames(this, queryArgs.genres, ", ") + : null); + + return new MangaListLoader(this, mProvider, queryArgs); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper result) { + mProgressBar.setVisibility(View.GONE); + if (result.isSuccess()) { + final ArrayList list = result.get(); + int firstPos = mDataset.size(); + mDataset.addAll(list); + if (firstPos == 0) { + mAdapter.notifyDataSetChanged(); + } else { + mAdapter.notifyItemRangeInserted(firstPos, list.size()); + } + mRecyclerView.onLoadingFinished(!list.isEmpty()); + } else { + setError(result.getError()); + mRecyclerView.onLoadingFinished(false); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.fabFilter: + final FilterDialogFragment dialogFragment = new FilterDialogFragment(); + final Bundle args = new Bundle(); + args.putIntArray("sorts", mProvider.getAvailableSortOrders()); + args.putParcelableArray("genres", mProvider.getAvailableGenres()); + args.putBundle("query", mArguments.toBundle()); + dialogFragment.setArguments(args); + dialogFragment.show(getSupportFragmentManager(), ""); + break; + case R.id.button_retry: + mProgressBar.setVisibility(View.VISIBLE); + case android.support.design.R.id.snackbar_action: + load(); + } + } + + @Override + public void setFilter(int sort, MangaGenre[] genres) { + final boolean theSame = sort == mArguments.sort && Arrays.equals(mArguments.genres, genres); + if (theSame) return; + mArguments.sort = sort; + mArguments.genres = genres; + loadFirstPage(); + } + + @Override + public boolean onQueryTextSubmit(String query) { + mMenuItemSearch.collapseActionView(); + if (TextUtils.equals(query, mArguments.query)) { + return true; + } + mArguments.query = query; + loadFirstPage(); + return true; + } + + /** + * Сбрасываем на первую страницу + */ + private void loadFirstPage() { + mArguments.page = 0; + mProgressBar.setVisibility(View.VISIBLE); + mDataset.clear(); + mAdapter.notifyDataSetChanged(); + load(); + } + + @Override + public boolean onQueryTextChange(String newText) { + return false; + } + + @Override + public boolean onLoadMore() { + mArguments.page++; + load(); + return true; + } + + @Override + public void onAuthorized() { + Snackbar.make(mRecyclerView, R.string.authorization_success, Snackbar.LENGTH_SHORT).show(); + mArguments.page = 0; + load(); + } + + private void setError(Throwable e) { + if (mDataset.isEmpty()) { + if (mErrorView instanceof ViewStub) { + mErrorView = ((ViewStub) mErrorView).inflate(); + mTextViewError = mErrorView.findViewById(R.id.textView_error); + mErrorView.findViewById(R.id.button_retry).setOnClickListener(this); + } + mTextViewError.setText(ErrorUtils.getErrorMessage(this, e)); + mErrorView.setVisibility(View.VISIBLE); + } else { + Snackbar.make(mRecyclerView, ErrorUtils.getErrorMessage(e), Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.retry, this) + .show(); + } + } + + /** + * Лоадер не перезагрузится если + * Bundle тот же самый с темиже значениями (вроде) + */ + private void load() { + mRecyclerView.onLoadingStarted(); + mErrorView.setVisibility(View.GONE); + /* + loader's id + */ + int LOADER_ID = 234; + Loader> loader = getLoaderManager().getLoader(LOADER_ID); + if (loader == null) { + loader = getLoaderManager().initLoader(LOADER_ID, mArguments.toBundle(), this); + } else { + loader = getLoaderManager().restartLoader(LOADER_ID, mArguments.toBundle(), this); + } + loader.forceLoad(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/MangaListAdapter.java b/app/src/main/java/org/nv95/openmanga/mangalist/MangaListAdapter.java new file mode 100644 index 00000000..33b6cd1e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/MangaListAdapter.java @@ -0,0 +1,225 @@ +package org.nv95.openmanga.mangalist; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.ColorInt; +import android.support.annotation.IntDef; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RatingBar; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.common.utils.ThemeUtils; +import org.nv95.openmanga.common.views.EndlessRecyclerView; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Created by koitharu on 28.12.17. + */ + +public final class MangaListAdapter extends RecyclerView.Adapter> implements + EndlessRecyclerView.EndlessAdapter { + + private final List mDataset; + private boolean mInProgress; + private boolean mDetailed; + + MangaListAdapter(List dataset, boolean detailed) { + setHasStableIds(true); + mDataset = dataset; + mInProgress = true; + mDetailed = detailed; + } + + @Override + public DataViewHolder onCreateViewHolder(ViewGroup parent, @ItemViewType int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + switch (viewType) { + case ItemViewType.TYPE_ITEM_MANGA: + return mDetailed ? + new DetailedMangaHolder(inflater.inflate(R.layout.item_manga_list_detailed, parent, false)) + : new MangaHolder(inflater.inflate(R.layout.item_manga_list, parent, false)); + case ItemViewType.TYPE_ITEM_PROGRESS: + return new ProgressHolder(inflater.inflate(R.layout.item_progress, parent, false)); + default: + throw new AssertionError("Unknown viewType"); + } + } + + @Override + public void onBindViewHolder(DataViewHolder holder, int position) { + if (holder instanceof MangaHolder) { + ((MangaHolder) holder).bind(mDataset.get(position)); + } else if (holder instanceof ProgressHolder) { + ((ProgressHolder) holder).bind(mInProgress); + } + } + + @Override + public int getItemCount() { + final int size = mDataset.size(); + return size == 0 ? 0 : size + 1; + } + + @ItemViewType + @Override + public int getItemViewType(int position) { + return position == mDataset.size() ? ItemViewType.TYPE_ITEM_PROGRESS : ItemViewType.TYPE_ITEM_MANGA; + } + + @Override + public long getItemId(int position) { + return position >= mDataset.size() ? 0 : mDataset.get(position).id; + } + + @Override + public void onViewRecycled(DataViewHolder holder) { + holder.recycle(); + super.onViewRecycled(holder); + } + + @Override + public void setHasNext(boolean hasNext) { + if (mInProgress != hasNext) { + mInProgress = hasNext; + if (!mDataset.isEmpty()) { + if (hasNext) { + notifyItemInserted(mDataset.size()); + } else { + notifyItemRemoved(mDataset.size()); + } + } + } + } + + public void setIsDetailed(boolean detailed) { + mDetailed = detailed; + } + + public boolean isDetailed() { + return mDetailed; + } + + static class MangaHolder extends DataViewHolder implements View.OnClickListener { + + private final TextView mText1; + private final TextView mText2; + private final TextView mTextViewSummary; + private final ImageView mImageView; + + MangaHolder(View itemView) { + super(itemView); + mText1 = itemView.findViewById(android.R.id.text1); + mText2 = itemView.findViewById(android.R.id.text2); + mTextViewSummary = itemView.findViewById(android.R.id.summary); + mImageView = itemView.findViewById(R.id.imageView); + itemView.setOnClickListener(this); + } + + public void bind(MangaHeader item) { + super.bind(item); + mText1.setText(item.name); + mTextViewSummary.setText(item.genres); + LayoutUtils.setTextOrHide(mText2, item.summary); + ImageUtils.setThumbnail(mImageView, item.thumbnail, MangaProvider.getDomain(item.provider)); + } + + @Override + public void recycle() { + super.recycle(); + ImageUtils.recycle(mImageView); + } + + @Override + public void onClick(View v) { + MangaHeader mangaHeader = getData(); + Context context = v.getContext(); + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", mangaHeader)); + } + } + + static class DetailedMangaHolder extends MangaHolder { + + private final RatingBar mRatingBar; + private final TextView mTextViewStatus; + @ColorInt + private final int mPrimaryColor; + @ColorInt + private final int mAccentColor; + + DetailedMangaHolder(View itemView) { + super(itemView); + mRatingBar = itemView.findViewById(R.id.ratingBar); + mTextViewStatus = itemView.findViewById(R.id.textView_status); + mPrimaryColor = ThemeUtils.getThemeAttrColor(itemView.getContext(), R.attr.colorPrimary); + mAccentColor = ThemeUtils.getThemeAttrColor(itemView.getContext(), R.attr.colorAccent); + } + + @Override + public void bind(MangaHeader item) { + super.bind(item); + if (item.rating == 0) { + mRatingBar.setVisibility(View.GONE); + } else { + mRatingBar.setVisibility(View.VISIBLE); + mRatingBar.setRating(item.rating / 20); + } + switch (item.status) { + case MangaStatus.STATUS_ONGOING: + mTextViewStatus.setVisibility(View.VISIBLE); + mTextViewStatus.setText(R.string.status_ongoing); + mTextViewStatus.setTextColor(mAccentColor); + break; + case MangaStatus.STATUS_COMPLETED: + mTextViewStatus.setVisibility(View.VISIBLE); + mTextViewStatus.setText(R.string.status_completed); + mTextViewStatus.setTextColor(mPrimaryColor); + break; + default: + mTextViewStatus.setVisibility(View.GONE); + + case MangaStatus.STATUS_UNKNOWN: + break; + } + } + } + + static class ProgressHolder extends DataViewHolder { + + private final ProgressBar mProgressBar; + + ProgressHolder(View itemView) { + super(itemView); + mProgressBar = itemView.findViewById(android.R.id.progress); + } + + @Override + public void bind(Boolean aBoolean) { + super.bind(aBoolean); + mProgressBar.setVisibility(aBoolean ? View.VISIBLE : View.INVISIBLE); + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ItemViewType.TYPE_ITEM_MANGA, ItemViewType.TYPE_ITEM_PROGRESS}) + public @interface ItemViewType { + int TYPE_ITEM_MANGA = 0; + int TYPE_ITEM_PROGRESS = 1; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/MangaListLoader.java b/app/src/main/java/org/nv95/openmanga/mangalist/MangaListLoader.java new file mode 100644 index 00000000..64802a4c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/MangaListLoader.java @@ -0,0 +1,39 @@ +package org.nv95.openmanga.mangalist; + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.util.Log; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.providers.MangaProvider; + +/** + * Created by koitharu on 28.12.17. + */ + +public final class MangaListLoader extends AsyncTaskLoader> { + + private final MangaProvider mProvider; + private final MangaQueryArguments mArguments; + + public MangaListLoader(Context context, MangaProvider mangaProvider, MangaQueryArguments arguments) { + super(context); + this.mProvider = mangaProvider; + mArguments = arguments; + } + + @Override + public ListWrapper loadInBackground() { + long time = System.currentTimeMillis(); + try { + return new ListWrapper<>(mProvider.query(mArguments.query, mArguments.page, mArguments.sort, mArguments.genresValues())); + } catch (Exception e) { + e.printStackTrace(); + return new ListWrapper<>(e); + } finally { + time = System.currentTimeMillis() - time; + Log.i("timing", String.format("%.2fs", time / 1000f)); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/MangaQueryArguments.java b/app/src/main/java/org/nv95/openmanga/mangalist/MangaQueryArguments.java new file mode 100644 index 00000000..d6f8e7ff --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/MangaQueryArguments.java @@ -0,0 +1,57 @@ +package org.nv95.openmanga.mangalist; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaGenre; + +/** + * Created by koitharu on 31.12.17. + */ + +public final class MangaQueryArguments { + + @Nullable + public String query; + public int page; + public int sort; + @NonNull + public MangaGenre[] genres; + + public MangaQueryArguments() { + genres = new MangaGenre[0]; + query = null; + page = 0; + } + + @NonNull + public Bundle toBundle() { + Bundle bundle = new Bundle(4); + bundle.putString("query", query); + bundle.putInt("page", page); + bundle.putInt("sort", sort); + bundle.putParcelableArray("genres", genres); + return bundle; + } + + @NonNull + public static MangaQueryArguments from(Bundle bundle) { + MangaQueryArguments args = new MangaQueryArguments(); + args.query = bundle.getString("query"); + args.page = bundle.getInt("page"); + args.sort = bundle.getInt("sort"); + //noinspection ConstantConditions + args.genres = (MangaGenre[]) bundle.getParcelableArray("genres"); + return args; + } + + @NonNull + public String[] genresValues() { + final String[] values = new String[genres.length]; + for (int i = 0; i < genres.length; i++) { + values[i] = genres[i].value; + } + return values; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesActivity.java b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesActivity.java new file mode 100644 index 00000000..1cce63d9 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesActivity.java @@ -0,0 +1,123 @@ +package org.nv95.openmanga.mangalist.favourites; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.storage.db.CategoriesRepository; +import org.nv95.openmanga.core.storage.db.CategoriesSpecification; +import org.nv95.openmanga.core.storage.settings.ShelfSettings; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.01.18. + */ + +public final class CategoriesActivity extends AppBaseActivity implements View.OnClickListener, DialogInterface.OnClickListener, CategoriesAdapter.OnClickListener { + + private RecyclerView mRecyclerView; + private CategoriesRepository mRepository; + private ArrayList mDataset; + private CategoriesAdapter mAdapter; + private AlertDialog mDialog; + private TextInputLayout mInputLayout; + private TextInputEditText mEditName; + private int mCurrentCategoryIndex; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_categories); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mRepository = CategoriesRepository.get(this); + mDataset = mRepository.query(new CategoriesSpecification().orderByDate(false)); + mAdapter = new CategoriesAdapter(mDataset, this); + + mRecyclerView = findViewById(R.id.recyclerView); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); + mRecyclerView.setAdapter(mAdapter); + + final View view = getLayoutInflater().inflate(R.layout.dialog_category_name, mRecyclerView, false); + mInputLayout = view.findViewById(R.id.inputLayout); + mEditName = view.findViewById(R.id.edit_name); + mDialog = new AlertDialog.Builder(this) + .setView(view) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.rename, this) + .create(); + + findViewById(R.id.fav_add).setOnClickListener(this); + } + + @Override + public void onClick(View v) { + mCurrentCategoryIndex = -1; + mEditName.setText(null); + mInputLayout.setError(null); + mDialog.setTitle(R.string.create_new_category); + mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.create), this); + mDialog.show(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final String name = mEditName.getText().toString(); + if (mCurrentCategoryIndex == -1) { + final Category category = new Category(name, System.currentTimeMillis()); + mRepository.add(category); + mDataset.add(category); + ShelfSettings.onCategoryAdded(this, category); + mAdapter.notifyItemInserted(mDataset.size() - 1); + setResult(RESULT_OK); + } else { + //TODO rename + // final Category category = mDataset.get(mCurrentCategoryIndex); + } + } + + @Override + public void onItemClick(int position) { + stub(); + } + + @Override + public void onItemActionClick(int position) { + if (mDataset.size() == 1) { + new AlertDialog.Builder(this) + .setMessage(R.string.favourites_category_must_be) + .setPositiveButton(android.R.string.ok, null) + .create() + .show(); + return; + } + final Category category = mDataset.get(position); + new AlertDialog.Builder(this) + .setMessage(getString(R.string.category_remove_confirm, category.name)) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.remove, (dialog, which) -> { + mRepository.remove(category); + mDataset.remove(category); + mAdapter.notifyDataSetChanged(); + Snackbar.make(mRecyclerView, getString(R.string.category_x_removed, category.name), Snackbar.LENGTH_SHORT) + .show(); + setResult(RESULT_OK); + }) + .create() + .show(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesAdapter.java b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesAdapter.java new file mode 100644 index 00000000..1a236d9b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesAdapter.java @@ -0,0 +1,82 @@ +package org.nv95.openmanga.mangalist.favourites; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.Category; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.01.18. + */ + +final class CategoriesAdapter extends RecyclerView.Adapter { + + private final OnClickListener mClickListener; + private final ArrayList mDataset; + + CategoriesAdapter(ArrayList dataset, OnClickListener clickListener) { + mDataset = dataset; + mClickListener = clickListener; + setHasStableIds(true); + } + + @Override + public CategoryHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new CategoryHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_category, parent, false)); + } + + @Override + public void onBindViewHolder(CategoryHolder holder, int position) { + holder.text1.setText(mDataset.get(position).name); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final ImageButton buttonRemove; + final TextView text1; + + CategoryHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + buttonRemove = itemView.findViewById(R.id.button_remove); + itemView.setOnClickListener(this); + buttonRemove.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + final int position = getAdapterPosition(); + switch (v.getId()) { + case R.id.button_remove: + mClickListener.onItemActionClick(position); + break; + default: + mClickListener.onItemClick(position); + } + } + } + + interface OnClickListener { + void onItemClick(int position); + void onItemActionClick(int position); + } +} + diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesPagerAdapter.java b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesPagerAdapter.java new file mode 100644 index 00000000..f7704de7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/CategoriesPagerAdapter.java @@ -0,0 +1,83 @@ +package org.nv95.openmanga.mangalist.favourites; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.NativeFragmentPagerAdapter; +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.storage.db.CategoriesRepository; +import org.nv95.openmanga.core.storage.db.CategoriesSpecification; +import org.nv95.openmanga.core.storage.db.FavouritesSpecification; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.01.18. + */ + +final class CategoriesPagerAdapter extends NativeFragmentPagerAdapter { + + @NonNull + private final CategoriesRepository mRepository; + @NonNull + private final CategoriesSpecification mSpecification; + @NonNull + private final ArrayList mDataset; + + public CategoriesPagerAdapter(@NonNull FragmentManager fm, @NonNull CategoriesRepository repository) { + super(fm); + mSpecification = new CategoriesSpecification() + .orderByDate(false); + mRepository = repository; + mDataset = repository.query(mSpecification); + } + + @NonNull + public CategoriesSpecification getSpecification() { + return mSpecification; + } + + @NonNull + @Override + public Fragment getItem(int position) { + final FavouritesFragment fragment = new FavouritesFragment(); + fragment.setArguments(new FavouritesSpecification() + .category(mDataset.get(position).id) + .orderByDate(true) + .toBundle()); + return fragment; + } + + @Override + public void notifyDataSetChanged() { + mDataset.clear(); + mDataset.addAll(mRepository.query(mSpecification)); + super.notifyDataSetChanged(); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return mDataset.get(position).name; + } + + @Override + public int getCount() { + return mDataset.size(); + } + + int indexById(int id) { + for (int i = 0; i < mDataset.size(); i++) { + if (mDataset.get(i).id == id) { + return i; + } + } + return -1; + } + + public ArrayList getData() { + return mDataset; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesActivity.java b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesActivity.java new file mode 100644 index 00000000..805d045a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesActivity.java @@ -0,0 +1,84 @@ +package org.nv95.openmanga.mangalist.favourites; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.core.storage.db.CategoriesRepository; + +/** + * Created by koitharu on 18.01.18. + */ + +public final class FavouritesActivity extends AppBaseActivity { + + private static final int REQUEST_CATEGORIES = 14; + + private ViewPager mPager; + private CategoriesPagerAdapter mPagerAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_favourites); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mPager = findViewById(R.id.pager); + TabLayout mTabs = findViewById(R.id.tabs); + + mPagerAdapter = new CategoriesPagerAdapter(getFragmentManager(), CategoriesRepository.get(this)); + mPager.setAdapter(mPagerAdapter); + mTabs.setupWithViewPager(mPager); + + final int category_id = getIntent().getIntExtra("category_id", 0); + if (category_id != 0) { + int index = mPagerAdapter.indexById(category_id); + if (index != -1) { + mPager.setCurrentItem(index, false); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_favourites, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_manage_categories: + startActivityForResult(new Intent(this, CategoriesActivity.class), REQUEST_CATEGORIES); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CATEGORIES && resultCode == RESULT_OK) { + int pos = mPager.getCurrentItem(); + int id = mPagerAdapter.getData().get(pos).id; + mPagerAdapter.notifyDataSetChanged(); + int newPos = CollectionsUtils.findCategoryPositionById(mPagerAdapter.getData(), id); + if (newPos == -1) { //removed current page + if (pos >= mPagerAdapter.getCount()) { //it was latest + mPager.setCurrentItem(mPagerAdapter.getCount() - 1); //switch to new latest + } + } else { + mPager.setCurrentItem(newPos, false); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesAdapter.java b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesAdapter.java new file mode 100644 index 00000000..277dd66c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesAdapter.java @@ -0,0 +1,94 @@ +package org.nv95.openmanga.mangalist.favourites; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.01.18. + */ + +final class FavouritesAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + + FavouritesAdapter(ArrayList dataset) { + setHasStableIds(true); + mDataset = dataset; + } + + @Override + public FavouriteHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new FavouriteHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_manga_list, parent, false)); + } + + @Override + public void onBindViewHolder(FavouriteHolder holder, int position) { + MangaFavourite item = mDataset.get(position); + holder.text1.setText(item.name); + LayoutUtils.setTextOrHide(holder.text2, item.summary); + holder.summary.setText(item.genres); + ImageUtils.setThumbnail(holder.imageView, item.thumbnail, MangaProvider.getDomain(item.provider)); + holder.itemView.setTag(item); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + @Override + public void onViewRecycled(FavouriteHolder holder) { + ImageUtils.recycle(holder.imageView); + super.onViewRecycled(holder); + } + + class FavouriteHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final TextView text1; + final TextView text2; + final TextView summary; + final ImageView imageView; + + FavouriteHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + text2 = itemView.findViewById(android.R.id.text2); + summary = itemView.findViewById(android.R.id.summary); + imageView = itemView.findViewById(R.id.imageView); + + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + final Context context = view.getContext(); + final MangaFavourite item = mDataset.get(getAdapterPosition()); + switch (view.getId()) { + default: + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", item)); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesFragment.java b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesFragment.java new file mode 100644 index 00000000..cbe1d325 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesFragment.java @@ -0,0 +1,103 @@ +package org.nv95.openmanga.mangalist.favourites; + +import android.app.Activity; +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.nv95.openmanga.AppBaseFragment; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; +import org.nv95.openmanga.core.storage.db.FavouritesSpecification; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.01.18. + */ + +public final class FavouritesFragment extends AppBaseFragment implements LoaderManager.LoaderCallbacks> { + + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + private FavouritesSpecification mSpecifications; + private FavouritesAdapter mAdapter; + private ArrayList mDataset; + + private FavouritesRepository mFavouritesRepository; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDataset = new ArrayList<>(); + mSpecifications = FavouritesSpecification.from(getArguments()); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_favourites, container,false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mProgressBar = view.findViewById(R.id.progressBar); + mRecyclerView = view.findViewById(R.id.recyclerView); + mTextViewHolder = view.findViewById(R.id.textView_holder); + + mRecyclerView.setHasFixedSize(true); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Activity activity = getActivity(); + mAdapter = new FavouritesAdapter(mDataset); + mRecyclerView.addItemDecoration(new DividerItemDecoration(activity, LinearLayoutManager.VERTICAL)); + mRecyclerView.setAdapter(mAdapter); + mFavouritesRepository = FavouritesRepository.get(activity); + getLoaderManager().initLoader((int) mSpecifications.getId(), mSpecifications.toBundle(), this).forceLoad(); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new FavouritesLoader(getActivity(), FavouritesSpecification.from(args)); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper result) { + mProgressBar.setVisibility(View.GONE); + if (result.isSuccess()) { + final ArrayList list = result.get(); + mDataset.clear(); + mDataset.addAll(list); + mAdapter.notifyDataSetChanged(); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + } else { + Snackbar.make(mRecyclerView, ErrorUtils.getErrorMessage(result.getError()), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesLoader.java b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesLoader.java new file mode 100644 index 00000000..2a9123e8 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/favourites/FavouritesLoader.java @@ -0,0 +1,33 @@ +package org.nv95.openmanga.mangalist.favourites; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; +import org.nv95.openmanga.core.storage.db.FavouritesSpecification; + +/** + * Created by koitharu on 18.01.18. + */ + +public final class FavouritesLoader extends AsyncTaskLoader> { + + private final FavouritesSpecification mSpec; + + public FavouritesLoader(Context context, FavouritesSpecification specification) { + super(context); + mSpec = specification; + } + + @Override + public ListWrapper loadInBackground() { + try { + return new ListWrapper<>(FavouritesRepository.get(getContext()).query(mSpec)); + } catch (Exception e) { + e.printStackTrace(); + return new ListWrapper<>(e); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryActivity.java b/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryActivity.java new file mode 100644 index 00000000..2c08b8a2 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryActivity.java @@ -0,0 +1,174 @@ +package org.nv95.openmanga.mangalist.history; + +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.UndoHelper; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.common.utils.MenuUtils; +import org.nv95.openmanga.common.views.recyclerview.SwipeRemoveHelper; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.storage.FlagsStorage; +import org.nv95.openmanga.core.storage.db.HistoryRepository; +import org.nv95.openmanga.core.storage.db.HistorySpecification; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.01.18. + */ + +public final class HistoryActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks>, + SwipeRemoveHelper.OnItemRemovedListener, UndoHelper.OnActionUndoCallback { + + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + private HistoryAdapter mAdapter; + private ArrayList mDataset; + private HistorySpecification mSpecifications; + private HistoryRepository mHistoryRepository; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_history); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mProgressBar = findViewById(R.id.progressBar); + mRecyclerView = findViewById(R.id.recyclerView); + mTextViewHolder = findViewById(R.id.textView_holder); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); + + mSpecifications = new HistorySpecification() + .orderByDate(true); + mHistoryRepository = HistoryRepository.get(this); + + mDataset = new ArrayList<>(); + mAdapter = new HistoryAdapter(mDataset, FlagsStorage.get(this).isHistoryDetailed()); + mRecyclerView.setAdapter(mAdapter); + SwipeRemoveHelper.setup(mRecyclerView, this); + + getLoaderManager().initLoader(0, mSpecifications.toBundle(), this).forceLoad(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_history, menu); + MenuUtils.setRadioCheckable(menu.findItem(R.id.action_sort), R.id.group_sort); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final String orderBy = mSpecifications.getOrderBy(); + if (orderBy != null && orderBy.contains("name")) { + menu.findItem(R.id.sort_name).setChecked(true); + } else { + menu.findItem(R.id.sort_latest).setChecked(true); + } + menu.findItem(R.id.option_details).setChecked(mAdapter.isDetailed()); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.sort_latest: + item.setChecked(true); + mSpecifications.orderByDate(true); + getLoaderManager().restartLoader(0, mSpecifications.toBundle(), this).forceLoad(); + return true; + case R.id.sort_name: + item.setChecked(true); + mSpecifications.orderByName(false); + getLoaderManager().restartLoader(0, mSpecifications.toBundle(), this).forceLoad(); + return true; + case R.id.option_details: + boolean checked = !item.isChecked(); + item.setChecked(checked); + FlagsStorage.get(this).setIsHistoryDetailed(checked); + mAdapter.setIsDetailed(checked); + LayoutUtils.forceUpdate(mRecyclerView); + return true; + case R.id.action_clear: + new AlertDialog.Builder(this) + .setMessage(R.string.history_clear_confirm) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.clear, (dialog, which) -> { + mHistoryRepository.clear(); + onLoadFinished(null, new ListWrapper<>(new ArrayList<>())); + Snackbar.make(mRecyclerView, R.string.history_cleared, Snackbar.LENGTH_LONG).show(); + }) + .create() + .show(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public Loader> onCreateLoader(int i, Bundle bundle) { + final HistorySpecification spec = HistorySpecification.from(bundle); + return new HistoryLoader(this, spec); + } + + @Override + public void onLoadFinished(@Nullable Loader> loader, ListWrapper result) { + mProgressBar.setVisibility(View.GONE); + if (result.isSuccess()) { + final ArrayList list = result.get(); + mDataset.clear(); + mDataset.addAll(list); + mAdapter.notifyDataSetChanged(); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + } else { + Snackbar.make(mRecyclerView, ErrorUtils.getErrorMessage(result.getError()), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public void onItemRemoved(int position) { + final MangaHistory item = mDataset.remove(position); + mHistoryRepository.remove(item); + mAdapter.notifyItemRemoved(position); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + new UndoHelper<>(position, this) + .snackbar(mRecyclerView, getString(R.string.removed_from_history, item.name), item, Snackbar.LENGTH_SHORT); + } + + @Override + public void onActionUndo(int actionId, MangaHistory data) { + mHistoryRepository.add(data); + mDataset.add(actionId, data); + mAdapter.notifyItemRemoved(actionId); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryAdapter.java b/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryAdapter.java new file mode 100644 index 00000000..d7dc0142 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryAdapter.java @@ -0,0 +1,167 @@ +package org.nv95.openmanga.mangalist.history; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.ColorInt; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RatingBar; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.utils.ThemeUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.01.18. + */ + +final class HistoryAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private boolean mDetailed; + + HistoryAdapter(ArrayList dataset, boolean detailed) { + setHasStableIds(true); + mDataset = dataset; + mDetailed = detailed; + } + + @Override + public HistoryHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + return mDetailed ? + new DetailedHistoryHolder(inflater.inflate(R.layout.item_manga_list_detailed, parent, false)) : + new HistoryHolder(inflater.inflate(R.layout.item_manga_list, parent, false)); + } + + @Override + public void onBindViewHolder(HistoryHolder holder, int position) { + holder.bind(mDataset.get(position)); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + @Override + public void onViewRecycled(HistoryHolder holder) { + holder.recycle(); + } + + public void setIsDetailed(boolean detailed) { + mDetailed = detailed; + } + + public boolean isDetailed() { + return mDetailed; + } + + static class HistoryHolder extends DataViewHolder implements View.OnClickListener { + + private final TextView mText1; + private final TextView mText2; + private final TextView mSummary; + private final ImageView mImageView; + + private HistoryHolder(View itemView) { + super(itemView); + mText1 = itemView.findViewById(android.R.id.text1); + mText2 = itemView.findViewById(android.R.id.text2); + mSummary = itemView.findViewById(android.R.id.summary); + mImageView = itemView.findViewById(R.id.imageView); + itemView.setOnClickListener(this); + } + + @Override + public void bind(MangaHistory item) { + super.bind(item); + mText1.setText(item.name); + mText2.setText(ResourceUtils.formatTimeRelative(item.updatedAt)); + mSummary.setText(item.genres); + ImageUtils.setThumbnail(mImageView, item.thumbnail, MangaProvider.getDomain(item.provider)); + } + + @Override + public void recycle() { + super.recycle(); + ImageUtils.recycle(mImageView); + } + + @Override + public void onClick(View view) { + final Context context = view.getContext(); + final MangaHistory item = getData(); + if (item != null) { + switch (view.getId()) { + default: + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", item)); + } + } + } + } + + static class DetailedHistoryHolder extends HistoryHolder { + + private final RatingBar mRatingBar; + private final TextView mTextViewStatus; + @ColorInt + private final int mPrimaryColor; + @ColorInt + private final int mAccentColor; + + private DetailedHistoryHolder(View itemView) { + super(itemView); + mRatingBar = itemView.findViewById(R.id.ratingBar); + mTextViewStatus = itemView.findViewById(R.id.textView_status); + mPrimaryColor = ThemeUtils.getThemeAttrColor(itemView.getContext(), R.attr.colorPrimary); + mAccentColor = ThemeUtils.getThemeAttrColor(itemView.getContext(), R.attr.colorAccent); + } + + @Override + public void bind(MangaHistory item) { + super.bind(item); + if (item.rating == 0) { + mRatingBar.setVisibility(View.GONE); + } else { + mRatingBar.setVisibility(View.VISIBLE); + mRatingBar.setRating(item.rating / 20); + } + switch (item.status) { + case MangaStatus.STATUS_ONGOING: + mTextViewStatus.setVisibility(View.VISIBLE); + mTextViewStatus.setText(R.string.status_ongoing); + mTextViewStatus.setTextColor(mAccentColor); + break; + case MangaStatus.STATUS_COMPLETED: + mTextViewStatus.setVisibility(View.VISIBLE); + mTextViewStatus.setText(R.string.status_completed); + mTextViewStatus.setTextColor(mPrimaryColor); + break; + default: + mTextViewStatus.setVisibility(View.GONE); + + case MangaStatus.STATUS_UNKNOWN: + break; + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryLoader.java b/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryLoader.java new file mode 100644 index 00000000..2aa65819 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/history/HistoryLoader.java @@ -0,0 +1,33 @@ +package org.nv95.openmanga.mangalist.history; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.storage.db.HistoryRepository; +import org.nv95.openmanga.core.storage.db.HistorySpecification; + +/** + * Created by koitharu on 18.01.18. + */ + +final class HistoryLoader extends AsyncTaskLoader> { + + private final HistorySpecification mSpec; + + public HistoryLoader(Context context, HistorySpecification specification) { + super(context); + mSpec = specification; + } + + @Override + public ListWrapper loadInBackground() { + try { + return new ListWrapper<>(HistoryRepository.get(getContext()).query(mSpec)); + } catch (Exception e) { + e.printStackTrace(); + return new ListWrapper<>(e); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/updates/MangaUpdatesActivity.java b/app/src/main/java/org/nv95/openmanga/mangalist/updates/MangaUpdatesActivity.java new file mode 100644 index 00000000..07a3e3fa --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/updates/MangaUpdatesActivity.java @@ -0,0 +1,133 @@ +package org.nv95.openmanga.mangalist.updates; + +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.OemBadgeHelper; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.views.recyclerview.SwipeRemoveHelper; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; +import org.nv95.openmanga.core.storage.db.FavouritesSpecification; +import org.nv95.openmanga.mangalist.favourites.FavouritesLoader; + +import java.util.ArrayList; + +/** + * Created by koitharu on 30.01.18. + */ + +public final class MangaUpdatesActivity extends AppBaseActivity implements SwipeRemoveHelper.OnItemRemovedListener, + LoaderManager.LoaderCallbacks> { + + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + private MangaUpdatesAdapter mAdapter; + private ArrayList mDataset; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_history); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mProgressBar = findViewById(R.id.progressBar); + mRecyclerView = findViewById(R.id.recyclerView); + mTextViewHolder = findViewById(R.id.textView_holder); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); + mTextViewHolder.setText(R.string.no_new_chapters); + + mDataset = new ArrayList<>(); + mAdapter = new MangaUpdatesAdapter(mDataset); + mRecyclerView.setAdapter(mAdapter); + SwipeRemoveHelper.setup(mRecyclerView, this, R.color.green_overlay, R.drawable.ic_done_all_white); + + getLoaderManager().initLoader(0, null, this).forceLoad(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_updates, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_clear: + new AlertDialog.Builder(this) + .setMessage(R.string.mark_all_viewed_confirm) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.yes, (dialog, which) -> { + FavouritesRepository.get(MangaUpdatesActivity.this).clearNewChapters(); + onLoadFinished(null, new ListWrapper<>(new ArrayList<>())); + Snackbar.make(mRecyclerView, R.string.chapters_marked_as_viewed, Snackbar.LENGTH_LONG).show(); + new OemBadgeHelper(MangaUpdatesActivity.this).applyCount(0); + }) + .create() + .show(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public Loader> onCreateLoader(int i, Bundle bundle) { + return new FavouritesLoader(this, new FavouritesSpecification().onlyWithNewChapters().orderByDate(true)); + } + + @Override + public void onLoadFinished(@Nullable Loader> loader, ListWrapper result) { + mProgressBar.setVisibility(View.GONE); + if (result.isSuccess()) { + final ArrayList list = result.get(); + mDataset.clear(); + mDataset.addAll(list); + mAdapter.notifyDataSetChanged(); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + } else { + Snackbar.make(mRecyclerView, ErrorUtils.getErrorMessage(result.getError()), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public void onItemRemoved(int position) { + final MangaFavourite item = mDataset.remove(position); + mAdapter.notifyItemRemoved(position); + FavouritesRepository.get(this).setNoUpdates(item); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + Snackbar.make(mRecyclerView, R.string.chapters_marked_as_viewed, Snackbar.LENGTH_SHORT).show(); + int total = 0; + for (MangaFavourite o : mDataset) { + total += o.newChapters; + } + new OemBadgeHelper(this).applyCount(total); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/mangalist/updates/MangaUpdatesAdapter.java b/app/src/main/java/org/nv95/openmanga/mangalist/updates/MangaUpdatesAdapter.java new file mode 100644 index 00000000..a846eae4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/mangalist/updates/MangaUpdatesAdapter.java @@ -0,0 +1,95 @@ +package org.nv95.openmanga.mangalist.updates; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 30.01.18. + */ + +final class MangaUpdatesAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + + MangaUpdatesAdapter(ArrayList dataset) { + setHasStableIds(true); + mDataset = dataset; + } + + @Override + public HistoryHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new HistoryHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_manga_list, parent, false)); + } + + @Override + public void onBindViewHolder(HistoryHolder holder, int position) { + MangaFavourite item = mDataset.get(position); + holder.text1.setText(item.name); + holder.text2.setText(holder.itemView.getResources().getQuantityString( + R.plurals.chapters_new, item.newChapters, item.newChapters + )); + holder.summary.setText(item.genres); + ImageUtils.setThumbnail(holder.imageView, item.thumbnail, MangaProvider.getDomain(item.provider)); + holder.itemView.setTag(item); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + @Override + public void onViewRecycled(HistoryHolder holder) { + ImageUtils.recycle(holder.imageView); + super.onViewRecycled(holder); + } + + class HistoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final TextView text1; + final TextView text2; + final TextView summary; + final ImageView imageView; + + HistoryHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + text2 = itemView.findViewById(android.R.id.text2); + summary = itemView.findViewById(android.R.id.summary); + imageView = itemView.findViewById(R.id.imageView); + + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + final Context context = view.getContext(); + final MangaFavourite item = mDataset.get(getAdapterPosition()); + switch (view.getId()) { + default: + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", item)); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/MangaDetailsLoader.java b/app/src/main/java/org/nv95/openmanga/preview/MangaDetailsLoader.java new file mode 100644 index 00000000..b8dbf115 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/MangaDetailsLoader.java @@ -0,0 +1,56 @@ +package org.nv95.openmanga.preview; + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.support.annotation.NonNull; + +import org.nv95.openmanga.core.ObjectWrapper; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.SavedChapter; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.providers.OfflineMangaProvider; +import org.nv95.openmanga.core.storage.db.SavedChaptersRepository; +import org.nv95.openmanga.core.storage.db.SavedChaptersSpecification; + +import java.util.ArrayList; + +/** + * Created by koitharu on 15.01.18. + */ + +final class MangaDetailsLoader extends AsyncTaskLoader> { + + private final MangaHeader mManga; + + public MangaDetailsLoader(Context context, MangaHeader mangaHeader) { + super(context); + mManga = mangaHeader; + } + + @Override + @NonNull + public ObjectWrapper loadInBackground() { + try { + final MangaProvider provider = MangaProvider.get(getContext(), mManga.provider); + final MangaDetails details = provider.getDetails(mManga); + if (!(provider instanceof OfflineMangaProvider)) { + final ArrayList savedChapters = SavedChaptersRepository.get(getContext()) + .query(new SavedChaptersSpecification().manga(mManga)); + if (savedChapters != null) { + for (SavedChapter o : savedChapters) { + MangaChapter ch = details.chapters.findItemById(o.id); + if (ch != null) { + ch.addFlag(MangaChapter.FLAG_CHAPTER_SAVED); + } + } + } + } + return new ObjectWrapper<>(details); + } catch (Exception e) { + e.printStackTrace(); + return new ObjectWrapper<>(e); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/PageHolder.java b/app/src/main/java/org/nv95/openmanga/preview/PageHolder.java new file mode 100644 index 00000000..1b3e6673 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/PageHolder.java @@ -0,0 +1,48 @@ +package org.nv95.openmanga.preview; + +import android.content.Context; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by koitharu on 22.01.18. + */ + +public abstract class PageHolder { + + @NonNull + private final View mView; + + public PageHolder(@NonNull ViewGroup parent, @LayoutRes int layoutResId) { + mView = onCreateView(parent, layoutResId); + onViewCreated(mView); + } + + @NonNull + protected View onCreateView(@NonNull ViewGroup parent, @LayoutRes int layoutResId) { + return LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false); + } + + protected abstract void onViewCreated(@NonNull View view); + + String getTitle() { + return String.valueOf(mView.getTag()); + } + + @NonNull + public final View getView() { + return mView; + } + + protected Context getContext() { + return mView.getContext(); + } + + protected String getString(@StringRes int resId) { + return mView.getContext().getString(resId); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/PagesAdapter.java b/app/src/main/java/org/nv95/openmanga/preview/PagesAdapter.java new file mode 100644 index 00000000..375a2284 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/PagesAdapter.java @@ -0,0 +1,47 @@ +package org.nv95.openmanga.preview; + +import android.support.annotation.NonNull; +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by koitharu on 22.01.18. + */ + +final class PagesAdapter extends PagerAdapter { + + private final PageHolder[] mPages; + + public PagesAdapter(PageHolder... pages) { + mPages = pages; + } + + @Override + public int getCount() { + return mPages.length; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + View view = mPages[position].getView(); + container.addView(view); + return view; + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + container.removeView((View) object); + } + + @Override + public CharSequence getPageTitle(int position) { + return mPages[position].getTitle(); + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/PreviewActivity.java b/app/src/main/java/org/nv95/openmanga/preview/PreviewActivity.java new file mode 100644 index 00000000..5fb73021 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/PreviewActivity.java @@ -0,0 +1,415 @@ +package org.nv95.openmanga.preview; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.LoaderManager; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.bottomappbar.BottomAppBar; +import android.support.design.widget.Snackbar; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.support.v7.widget.SearchView; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.dialogs.FavouriteDialog; +import org.nv95.openmanga.common.dialogs.MenuDialog; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.BroadcastUtils; +import org.nv95.openmanga.common.utils.IntentUtils; +import org.nv95.openmanga.common.utils.MenuUtils; +import org.nv95.openmanga.core.ObjectWrapper; +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.storage.db.BookmarkSpecification; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; +import org.nv95.openmanga.core.storage.db.HistoryRepository; +import org.nv95.openmanga.discover.bookmarks.BookmarkRemoveTask; +import org.nv95.openmanga.preview.bookmarks.BookmarksPage; +import org.nv95.openmanga.preview.chapters.ChaptersListAdapter; +import org.nv95.openmanga.preview.chapters.ChaptersPage; +import org.nv95.openmanga.preview.details.DetailsPage; +import org.nv95.openmanga.reader.ReaderActivity; +import org.nv95.openmanga.storage.LocalRemoveService; +import org.nv95.openmanga.storage.SaveRequest; +import org.nv95.openmanga.storage.SaveService; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class PreviewActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks>, + ChaptersListAdapter.OnChapterClickListener, View.OnClickListener, BookmarkRemoveTask.OnBookmarkRemovedListener, + FavouriteDialog.OnFavouriteListener, MenuDialog.OnMenuItemClickListener, ViewPager.OnPageChangeListener, SearchView.OnQueryTextListener, BroadcastUtils.DownloadsReceiverCallback { + + public static final String ACTION_PREVIEW = "org.nv95.openmanga.ACTION_PREVIEW"; + + //views + //activity + private ViewPager mPager; + private ProgressBar mProgressBar; + private BottomAppBar mBottomBar; + private SearchView mSearchView; + //tabs + private DetailsPage mDetailsPage; + private ChaptersPage mChaptersPage; + private BookmarksPage mBookmarksPage; + //data + private MangaHeader mMangaHeader; + @Nullable + private MangaDetails mMangaDetails; + private HistoryRepository mHistory; + private FavouritesRepository mFavourites; + private BroadcastReceiver mDownloadsReceiver; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_preview); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mPager = findViewById(R.id.pager); + mBottomBar = findViewById(R.id.bottomBar); + mProgressBar = findViewById(R.id.progressBar); + TabLayout mTabs = findViewById(R.id.tabs); + mBottomBar.inflateMenu(R.menu.options_preview_bottombar); + mSearchView = (SearchView) mBottomBar.getMenu().findItem(R.id.action_find_chapter).getActionView(); + mBottomBar.setOnMenuItemClickListener(this::onOptionsItemSelected); + mSearchView.setQueryHint(getString(R.string.find_chapter)); + mSearchView.setOnQueryTextListener(this); + + final PagesAdapter adapter = new PagesAdapter( + mDetailsPage = new DetailsPage(mPager), + mChaptersPage = new ChaptersPage(mPager), + mBookmarksPage = new BookmarksPage(mPager) + ); + mPager.setAdapter(adapter); + mTabs.setupWithViewPager(mPager); + mPager.addOnPageChangeListener(this); + + mDetailsPage.buttonRead.setOnClickListener(this); + mDetailsPage.buttonFavourite.setOnClickListener(this); + + mMangaHeader = getIntent().getParcelableExtra("manga"); + mMangaDetails = null; + assert mMangaHeader != null; + mHistory = HistoryRepository.get(this); + mFavourites = FavouritesRepository.get(this); + + setTitle(mMangaHeader.name); + setSubtitle(mMangaHeader.summary); + mDetailsPage.updateContent(mMangaHeader, mMangaDetails); + + Bundle args = new Bundle(1); + args.putParcelable("manga", mMangaHeader); + getLoaderManager().initLoader(0, args, this).forceLoad(); + getLoaderManager().initLoader(1, new BookmarkSpecification().manga(mMangaHeader).orderByDate(true).toBundle(), mBookmarksPage).forceLoad(); + + mDownloadsReceiver = BroadcastUtils.createDownloadsReceiver(this); + registerReceiver(mDownloadsReceiver, new IntentFilter(BroadcastUtils.ACTION_DOWNLOAD_DONE)); + } + + @Override + protected void onDestroy() { + unregisterReceiver(mDownloadsReceiver); + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_preview, menu); + MenuUtils.buildOpenWithSubmenu(this, mMangaHeader, menu.findItem(R.id.action_open_ext)); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final int page = mPager.getCurrentItem(); + menu.setGroupVisible(R.id.group_details, page == 0); + menu.setGroupVisible(R.id.group_chapters, page == 1); + return super.onPrepareOptionsMenu(menu); + } + + private void onPrepareBottomBarMenu(Menu menu) { + final MenuItem item = menu.findItem(R.id.action_reverse); + item.setIcon(item.isChecked() ? R.drawable.ic_sort_numeric_reverse_white : R.drawable.ic_sort_numeric_white); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_share: + IntentUtils.shareManga(this, mMangaHeader); + return true; + case R.id.action_shortcut: + IntentUtils.createLauncherShortcutPreview(this, mMangaHeader); + return true; + case R.id.action_reverse: + boolean reversed = !item.isChecked(); + if (mChaptersPage.setReversed(reversed)) { + item.setChecked(reversed); + item.setIcon(reversed ? R.drawable.ic_sort_numeric_reverse_white : R.drawable.ic_sort_numeric_white); + return true; + } else { + return false; + } + case R.id.action_relative: + stub(); + return true; + case R.id.action_save: + stub(); + return true; + case R.id.action_chapter_save_all: + case R.id.action_chapter_remove_all: + onMenuItemClick(item.getItemId(), null); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onResume() { + super.onResume(); + final MangaHistory history = mHistory.find(mMangaHeader); + if (history != null) { + mChaptersPage.updateHistory(history); + mDetailsPage.buttonRead.setText(R.string._continue); + } + final MangaFavourite favourite = mFavourites.get(mMangaHeader); + mDetailsPage.buttonFavourite.setImageResource(favourite == null ? R.drawable.ic_favorite_outline_light : R.drawable.ic_favorite_light); + } + + @NonNull + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new MangaDetailsLoader(this, args.getParcelable("manga")); + } + + @Override + public void onLoadFinished(Loader> loader, ObjectWrapper data) { + mProgressBar.setVisibility(View.GONE); + if (data.isSuccess()) { + mMangaDetails = data.get(); + assert mMangaDetails != null; + setTitle(mMangaDetails.name); + setSubtitle(mMangaDetails.summary); + final MangaHistory history = mHistory.find(mMangaHeader); + /*if (history != null) { + int pos = CollectionsUtils.findChapterPositionById(mMangaDetails.chapters, history.chapterId); + if (pos != -1 && LayoutUtils.findLastVisibleItemPosition(mRecyclerView) > pos) { + mRecyclerView.scrollToPosition(Math.max(0, pos - 3)); + } + }*/ + mDetailsPage.updateContent(mMangaHeader, mMangaDetails); + mChaptersPage.setData(mMangaDetails.chapters, history, this); + } else { + mChaptersPage.setError(); + mDetailsPage.setError(data.getError()); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public void onChapterClick(int pos, MangaChapter chapter) { + startActivity(new Intent(this, ReaderActivity.class) + .putExtra("manga", mMangaDetails) + .putExtra("chapter", chapter)); + } + + @Override + public boolean onChapterLongClick(int pos, MangaChapter chapter) { + final int totalChapters = mMangaDetails == null ? 0 : mMangaDetails.chapters.size(); + MenuDialog menu = new MenuDialog(this) + .setTitle(chapter.name) + .setItemClickListener(this); + if (chapter.isSaved()) { + menu.addItem(R.id.action_chapter_remove_this, R.string.remove_this_chapter); + menu.addItem(R.id.action_chapter_remove_all, R.string.remove_all_chapters); + } else { + menu.addItem(R.id.action_chapter_save_this, R.string.save_this_chapter); + if (pos > 0) { + menu.addItem(R.id.action_chapter_save_prev, R.string.save_prev_chapters); + } + if (pos + 5 < totalChapters) { + menu.addItem(R.id.action_chapter_save_5, R.string.save_next_5_chapters); + if (pos + 10 < totalChapters) { + menu.addItem(R.id.action_chapter_save_10, R.string.save_next_10_chapters); + if (pos + 30 < totalChapters) { + menu.addItem(R.id.action_chapter_save_30, R.string.save_next_30_chapters); + } + } + } + if (pos < totalChapters - 1) { + menu.addItem(R.id.action_chapter_save_next, R.string.save_next_all_chapters); + } + menu.addItem(R.id.action_chapter_save_all, R.string.save_all_chapters); + } + menu.create(chapter) + .show(); + return true; + } + + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.button_read: + startActivity(new Intent(this, ReaderActivity.class) + .setAction(ReaderActivity.ACTION_READING_CONTINUE) + .putExtra("manga", mMangaDetails)); + break; + case R.id.button_favourite: + if (mMangaDetails != null) { + new FavouriteDialog(this, mMangaDetails) + .setListener(this) + .show(); + } + break; + } + } + + @Override + public void onFavouritesChanged(MangaDetails manga, @Nullable Category category) { + Snackbar.make( + mPager, + category == null ? getString(R.string.unfavourited) : getString(R.string.added_to_x, category.name), + Snackbar.LENGTH_SHORT + ).show(); + mDetailsPage.buttonFavourite.setImageResource(category == null ? R.drawable.ic_favorite_outline_light : R.drawable.ic_favorite_light); + } + + @Override + public void onMenuItemClick(@IdRes int id, MangaChapter mangaChapter) { + if (mMangaDetails == null) { + return; + } + switch (id) { + case R.id.action_chapter_save_this: + SaveService.start(this, new SaveRequest(mMangaDetails, mangaChapter)); + Snackbar.make(mPager, R.string.downloading_started_, Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_save_5: + SaveService.start(this, new SaveRequest(mMangaDetails, mMangaDetails.chapters.subListFrom(mangaChapter, 5))); + Snackbar.make(mPager, R.string.downloading_started_, Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_save_10: + SaveService.start(this, new SaveRequest(mMangaDetails, mMangaDetails.chapters.subListFrom(mangaChapter, 10))); + Snackbar.make(mPager, R.string.downloading_started_, Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_save_30: + SaveService.start(this, new SaveRequest(mMangaDetails, mMangaDetails.chapters.subListFrom(mangaChapter, 30))); + Snackbar.make(mPager, R.string.downloading_started_, Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_save_all: + SaveService.start(this, new SaveRequest(mMangaDetails, mMangaDetails.chapters)); + Snackbar.make(mPager, R.string.downloading_started_, Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_save_next: + SaveService.start(this, new SaveRequest(mMangaDetails, mMangaDetails.chapters.subListFrom(mangaChapter))); + Snackbar.make(mPager, R.string.downloading_started_, Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_save_prev: + SaveService.start(this, new SaveRequest(mMangaDetails, mMangaDetails.chapters.subListTo(mangaChapter))); + Snackbar.make(mPager, R.string.downloading_started_, Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_remove_this: + LocalRemoveService.start(this, mMangaHeader, mangaChapter); + mangaChapter.removeFlag(MangaChapter.FLAG_CHAPTER_SAVED); + mChaptersPage.update(); + Snackbar.make(mPager, getResources().getQuantityString(R.plurals.chapters_removed, 1, 1), Snackbar.LENGTH_SHORT).show(); + break; + case R.id.action_chapter_remove_all: + LocalRemoveService.start(this, mMangaHeader); + int c = 0; + for (MangaChapter o : mMangaDetails.chapters) { + if (o.isSaved()) { + c++; + o.removeFlag(MangaChapter.FLAG_CHAPTER_SAVED); + } + } + mChaptersPage.update(); + Snackbar.make(mPager, getResources().getQuantityString(R.plurals.chapters_removed, c, c), Snackbar.LENGTH_SHORT).show(); + break; + default: + stub(); + } + } + + @Override + public void onBookmarkRemoved(@NonNull MangaBookmark bookmark) { + mBookmarksPage.onBookmarkRemoved(bookmark); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + invalidateOptionsMenu(); + if (position == 1) { + onPrepareBottomBarMenu(mBottomBar.getMenu()); + mBottomBar.setVisibility(View.VISIBLE); + mBottomBar.animate().translationY(0).setListener(null).start(); + //AnimationUtils.setVisibility(mBottomBar, View.VISIBLE); + } else { + mBottomBar.animate().translationY(mBottomBar.getHeight()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBottomBar.setVisibility(View.GONE); + } + }).start(); + //AnimationUtils.setVisibility(mBottomBar, View.GONE); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + + @Override + public boolean onQueryTextSubmit(String s) { + return false; + } + + @Override + public boolean onQueryTextChange(String s) { + mChaptersPage.setFilter(s); + return false; + } + + @Override + public void onChapterDownloaded(MangaChapter chapter) { + if (mMangaDetails != null) { + final int pos = mMangaDetails.chapters.indexOf(chapter); + if (pos != -1) { + mMangaDetails.chapters.get(pos).addFlag(MangaChapter.FLAG_CHAPTER_SAVED); + mChaptersPage.update(pos); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/bookmarks/BookmarksAdapter.java b/app/src/main/java/org/nv95/openmanga/preview/bookmarks/BookmarksAdapter.java new file mode 100644 index 00000000..5944ca20 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/bookmarks/BookmarksAdapter.java @@ -0,0 +1,136 @@ +package org.nv95.openmanga.preview.bookmarks; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.v7.widget.PopupMenu; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.IntentUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; +import org.nv95.openmanga.discover.bookmarks.BookmarkRemoveTask; +import org.nv95.openmanga.reader.ReaderActivity; +import org.nv95.openmanga.reader.ToolButtonCompat; + +import java.util.ArrayList; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class BookmarksAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private final ThumbnailsStorage mThumbStore; + + BookmarksAdapter(ArrayList dataset, ThumbnailsStorage thumbnailsStorage) { + mDataset = dataset; + mThumbStore = thumbnailsStorage; + setHasStableIds(true); + } + + @NonNull + @Override + public BookmarkHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new BookmarkHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_bookmark_grid, parent, false)); + } + + @Override + public void onBindViewHolder(BookmarkHolder holder, int position) { + holder.bind(mDataset.get(position)); + } + + @Override + public void onViewRecycled(BookmarkHolder holder) { + holder.recycle(); + super.onViewRecycled(holder); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + class BookmarkHolder extends DataViewHolder implements View.OnClickListener, PopupMenu.OnMenuItemClickListener, View.OnLongClickListener { + + private final ImageView mImageView; + private final TextView mTextView; + private final ToolButtonCompat mToolButtonMenu; + private final PopupMenu mPopupMenu; + + BookmarkHolder(View itemView) { + super(itemView); + mTextView = itemView.findViewById(R.id.textView); + mImageView = itemView.findViewById(R.id.imageView); + mToolButtonMenu = itemView.findViewById(R.id.toolButton_menu); + mPopupMenu = new PopupMenu(itemView.getContext(), mToolButtonMenu); + mPopupMenu.inflate(R.menu.popup_bookmark); + mPopupMenu.setOnMenuItemClickListener(this); + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + mToolButtonMenu.setOnClickListener(this); + } + + @Override + public void bind(MangaBookmark bookmark) { + super.bind(bookmark); + ImageUtils.setThumbnail(mImageView, mThumbStore.getFile(bookmark)); + mTextView.setText(ResourceUtils.formatTimeRelative(bookmark.createdAt)); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.toolButton_menu: + mPopupMenu.show(); + break; + default: + final Context context = view.getContext(); + context.startActivity(new Intent(context, ReaderActivity.class) + .setAction(ReaderActivity.ACTION_BOOKMARK_OPEN) + .putExtra("bookmark", mDataset.get(getAdapterPosition()))); + } + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + final MangaBookmark data = getData(); + if (data == null) { + return false; + } + switch (item.getItemId()) { + case R.id.action_remove: + new BookmarkRemoveTask(itemView.getContext()).start(data); + return true; + case R.id.action_shortcut: + IntentUtils.createLauncherShortcutRead(itemView.getContext().getApplicationContext(), data); + return true; + default: + return false; + } + } + + @Override + public boolean onLongClick(View v) { + onClick(mToolButtonMenu); + return true; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/bookmarks/BookmarksPage.java b/app/src/main/java/org/nv95/openmanga/preview/bookmarks/BookmarksPage.java new file mode 100644 index 00000000..6f5399fa --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/bookmarks/BookmarksPage.java @@ -0,0 +1,81 @@ +package org.nv95.openmanga.preview.bookmarks; + +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.MetricsUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.views.recyclerview.SpaceItemDecoration; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.storage.db.BookmarkSpecification; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; +import org.nv95.openmanga.discover.bookmarks.BookmarkRemoveTask; +import org.nv95.openmanga.discover.bookmarks.BookmarksLoader; +import org.nv95.openmanga.preview.PageHolder; + +import java.util.ArrayList; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class BookmarksPage extends PageHolder implements LoaderManager.LoaderCallbacks>, + BookmarkRemoveTask.OnBookmarkRemovedListener { + + public RecyclerView mRecyclerView; + private TextView mTextViewHolder; + private final ArrayList mDataset = new ArrayList<>(); + private final BookmarksAdapter mAdapter; + + + public BookmarksPage(@NonNull ViewGroup parent) { + super(parent, R.layout.page_manga_bookmarks); + mAdapter = new BookmarksAdapter(mDataset, new ThumbnailsStorage(parent.getContext())); + mRecyclerView.setAdapter(mAdapter); + } + + @Override + protected void onViewCreated(@NonNull View view) { + final int spans = MetricsUtils.getPreferredColumnsCountMedium(view.getResources()); + mRecyclerView = view.findViewById(R.id.recyclerView); + mRecyclerView.addItemDecoration(new SpaceItemDecoration(ResourceUtils.dpToPx(view.getResources(), 1))); + mRecyclerView.setLayoutManager(new GridLayoutManager(view.getContext(), spans)); + mTextViewHolder = view.findViewById(R.id.textView_holder); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new BookmarksLoader(getView().getContext(), BookmarkSpecification.from(args)); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper data) { + if (data.isSuccess()) { + mDataset.clear(); + mDataset.addAll(data.get()); + mTextViewHolder.setVisibility(data.isEmpty() ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public void onBookmarkRemoved(@NonNull MangaBookmark bookmark) { + mDataset.remove(bookmark); + mAdapter.notifyDataSetChanged(); + Snackbar.make(mRecyclerView, R.string.bookmark_removed, Snackbar.LENGTH_SHORT).show(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/chapters/ChaptersListAdapter.java b/app/src/main/java/org/nv95/openmanga/preview/chapters/ChaptersListAdapter.java new file mode 100644 index 00000000..b10c9c3a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/chapters/ChaptersListAdapter.java @@ -0,0 +1,130 @@ +package org.nv95.openmanga.preview.chapters; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.TextViewCompat; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ThemeUtils; +import org.nv95.openmanga.core.models.MangaChapter; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * Created by koitharu on 26.12.17. + */ + +public final class ChaptersListAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private final OnChapterClickListener mClickListener; + private long mCurrentId; + private boolean mReversed = false; + private final Drawable[] mIcons; + + public ChaptersListAdapter(Context context, ArrayList dataset, OnChapterClickListener listener) { + mClickListener = listener; + mDataset = dataset; + mCurrentId = 0; + setHasStableIds(true); + mIcons = new Drawable[] { + ContextCompat.getDrawable(context, R.drawable.ic_play_green), + ThemeUtils.getColoredDrawable(context, R.drawable.ic_save_white, android.R.attr.textColorTertiary) + }; + } + + public void setCurrentChapterId(long id) { + mCurrentId = id; + } + + public void reverse() { + Collections.reverse(mDataset); + notifyDataSetChanged(); + mReversed = !mReversed; + } + + public boolean isReversed() { + return mReversed; + } + + @Override + public ChapterHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ChapterHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_single_line, parent, false), mClickListener); + } + + @Override + public void onBindViewHolder(ChapterHolder holder, int position) { + MangaChapter ch = mDataset.get(position); + TextView tv = holder.getTextView(); + tv.setText(ch.name); + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds( + tv, + ch.id == mCurrentId ? mIcons[0] : null, + null, + ch.isSaved() ? mIcons[1] : null, + null + ); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + public int findByNameContains(String what) { + final String whatLower = what.toLowerCase(); + for (int i = 0, datasetSize = mDataset.size(); i < datasetSize; i++) { + MangaChapter o = mDataset.get(i); + if (o.name.toLowerCase().contains(whatLower)) { + return i; + } + } + return -1; + } + + final class ChapterHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + + private final OnChapterClickListener mListener; + + ChapterHolder(View itemView, OnChapterClickListener listener) { + super(itemView); + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + mListener = listener; + } + + public TextView getTextView() { + return (TextView) itemView; + } + + @Override + public void onClick(View v) { + final int pos = getAdapterPosition(); + mListener.onChapterClick(pos, mDataset.get(pos)); + } + + @Override + public boolean onLongClick(View view) { + final int pos = getAdapterPosition(); + return mListener.onChapterLongClick(pos, mDataset.get(pos)); + } + } + + public interface OnChapterClickListener { + void onChapterClick(int pos, MangaChapter chapter); + boolean onChapterLongClick(int pos, MangaChapter chapter); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/chapters/ChaptersPage.java b/app/src/main/java/org/nv95/openmanga/preview/chapters/ChaptersPage.java new file mode 100644 index 00000000..0d0e6b47 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/chapters/ChaptersPage.java @@ -0,0 +1,106 @@ +package org.nv95.openmanga.preview.chapters; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.core.models.MangaChaptersList; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.preview.PageHolder; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class ChaptersPage extends PageHolder { + + private RecyclerView mRecyclerViewChapters; + private TextView mTextViewChaptersHolder; + @Nullable + private ChaptersListAdapter mChaptersAdapter; + + public ChaptersPage(@NonNull ViewGroup parent) { + super(parent, R.layout.page_manga_chapters); + } + + @Override + protected void onViewCreated(@NonNull View view) { + mRecyclerViewChapters = view.findViewById(R.id.recyclerView_chapters); + mRecyclerViewChapters.addItemDecoration(new DividerItemDecoration(view.getContext(), LinearLayoutManager.VERTICAL)); + mTextViewChaptersHolder = view.findViewById(R.id.textView_chapters_holder); + } + + public void setData(MangaChaptersList chapters, @Nullable MangaHistory history, + ChaptersListAdapter.OnChapterClickListener chapterClickListener) { + mChaptersAdapter = new ChaptersListAdapter(getContext(), chapters, chapterClickListener); + if (history == null) { + mChaptersAdapter.setCurrentChapterId(0); + } else { + mChaptersAdapter.setCurrentChapterId(history.chapterId); + } + mRecyclerViewChapters.setAdapter(mChaptersAdapter); + if (chapters.isEmpty()) { + mTextViewChaptersHolder.setText(R.string.no_chapters_found); + mTextViewChaptersHolder.setVisibility(View.VISIBLE); + } else { + mTextViewChaptersHolder.setVisibility(View.GONE); + } + } + + public void setError() { + mTextViewChaptersHolder.setText(R.string.failed_to_load_chapters); + mTextViewChaptersHolder.setVisibility(View.VISIBLE); + } + + public void updateHistory(MangaHistory history) { + if (mChaptersAdapter != null) { + mChaptersAdapter.setCurrentChapterId(history.chapterId); + mChaptersAdapter.notifyDataSetChanged(); + } + } + + public boolean setReversed(boolean reversed) { + if (mChaptersAdapter != null && mChaptersAdapter.isReversed() != reversed) { + final int topPos = LayoutUtils.findLastVisibleItemPosition(mRecyclerViewChapters); + mChaptersAdapter.reverse(); + if (topPos != -1) { + int newPos = mChaptersAdapter.getItemCount() - topPos - 1; + /*if (newPos <= mChaptersAdapter.getItemCount() - 1) { + newPos += 2; //toolbar + }*/ + LayoutUtils.setSelectionFromTop(mRecyclerViewChapters, newPos); + } + return true; + } else { + return false; + } + } + + public void setFilter(String str) { + final int pos = mChaptersAdapter != null ? mChaptersAdapter.findByNameContains(str) : -1; + if (pos != -1) { + LayoutUtils.setSelectionFromTop(mRecyclerViewChapters, pos); + } + } + + public void update() { + update(-1); + } + + public void update(int index) { + if (mChaptersAdapter != null) { + if (index == -1) { + mChaptersAdapter.notifyDataSetChanged(); + } else { + mChaptersAdapter.notifyItemChanged(index); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/preview/details/DetailsPage.java b/app/src/main/java/org/nv95/openmanga/preview/details/DetailsPage.java new file mode 100644 index 00000000..1f16609c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/preview/details/DetailsPage.java @@ -0,0 +1,109 @@ +package org.nv95.openmanga.preview.details; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.RatingBar; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.common.utils.TextUtils; +import org.nv95.openmanga.core.MangaStatus; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PageHolder; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class DetailsPage extends PageHolder { + + private ImageView mImageViewCover; + private TextView mTextViewSummary; + private RatingBar mRatingBar; + private TextView mTextViewGenres; + private TextView mTextViewDescription; + + public Button buttonRead; + public ImageButton buttonFavourite; + + public DetailsPage(@NonNull ViewGroup parent) { + super(parent, R.layout.page_manga_details); + } + + @Override + protected void onViewCreated(@NonNull View view) { + mImageViewCover = view.findViewById(R.id.imageView_cover); + mTextViewSummary = view.findViewById(R.id.textView_summary); + mRatingBar = view.findViewById(R.id.ratingBar); + mTextViewDescription = view.findViewById(R.id.textView_description); + mTextViewGenres = view.findViewById(R.id.textView_genres); + buttonRead = view.findViewById(R.id.button_read); + buttonFavourite = view.findViewById(R.id.button_favourite); + } + + public void updateContent(@NonNull MangaHeader mangaHeader, @Nullable MangaDetails mangaDetails) { + final MangaProvider provider = MangaProvider.get(getContext(), mangaHeader.provider); + if (mangaDetails == null) { //full info wasn't loaded yet + ImageUtils.setThumbnail(mImageViewCover, mangaHeader.thumbnail, MangaProvider.getDomain(mangaHeader.provider)); + LayoutUtils.setTextOrHide(mTextViewGenres, mangaHeader.genres); + if (mangaHeader.rating == 0) { + mRatingBar.setVisibility(View.GONE); + } else { + mRatingBar.setVisibility(View.VISIBLE); + mRatingBar.setRating(mangaHeader.rating / 20); + } + mTextViewSummary.setText(formatSummary(null, -1, provider.getName(), mangaHeader.status)); + } else { + ImageUtils.updateImage(mImageViewCover, mangaDetails.cover, MangaProvider.getDomain(mangaDetails.provider)); + LayoutUtils.setTextOrHide(mTextViewGenres, mangaDetails.genres); + mTextViewDescription.setText(TextUtils.fromHtmlCompat(mangaDetails.description)); + if (mangaDetails.rating != 0) { + mRatingBar.setVisibility(View.VISIBLE); + mRatingBar.setRating(mangaDetails.rating / 20); + } + mTextViewSummary.setText(formatSummary(mangaDetails.author, mangaDetails.chapters.size(), provider.getName(), mangaDetails.status)); + } + } + + public void setError(@Nullable Throwable error) { + mTextViewDescription.setText(ErrorUtils.getErrorMessage(error)); + } + + @NonNull + private CharSequence formatSummary(@Nullable String author, int chapters, String provider, @MangaStatus int status) { + final StringBuilder builder = new StringBuilder(); + if (!android.text.TextUtils.isEmpty(author)) { + builder.append("").append(getString(R.string.author_)).append(" "); + builder.append(author).append("
"); + } + builder.append("").append(getString(R.string.chapters_count_)).append(" "); + if (chapters == -1) { + builder.append("?"); + } else { + builder.append(chapters); + } + builder.append("
").append("").append(getString(R.string.source_)).append(" "); + builder.append(provider); + switch (status) { + case MangaStatus.STATUS_COMPLETED: + builder.append("
").append(getString(R.string.status_completed)); + break; + case MangaStatus.STATUS_ONGOING: + builder.append("
").append(getString(R.string.status_ongoing)); + break; + case MangaStatus.STATUS_UNKNOWN: + break; + } + return TextUtils.fromHtmlCompat(builder.toString()); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/providers/AppUpdatesProvider.java b/app/src/main/java/org/nv95/openmanga/providers/AppUpdatesProvider.java deleted file mode 100644 index c31ca5b3..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/AppUpdatesProvider.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; - -import org.json.JSONException; -import org.json.JSONObject; -import org.nv95.openmanga.BuildConfig; -import org.nv95.openmanga.utils.NetworkUtils; - -import java.util.ArrayList; - -/** - * Created by nv95 on 19.02.16. - */ -public class AppUpdatesProvider { - - @Nullable - private final JSONObject mResponse; - - @WorkerThread - public AppUpdatesProvider() { - JSONObject response; - try { - response = NetworkUtils.getJsonObject(BuildConfig.SELFUPDATE_URL); - } catch (Exception e) { - response = null; - } - mResponse = response; - } - - public boolean isSuccess() { - try { - return mResponse != null && "success".equals(mResponse.getString("status")); - } catch (JSONException e) { - return false; - } - } - - public AppUpdateInfo[] getLatestUpdates() { - final ArrayList updates = new ArrayList<>(); - if (mResponse != null) { - AppUpdateInfo update = getLatestRelease(); - if (update != null && update.isActual()) { - update.versionName = "Latest release: v" + update.versionName; - updates.add(update); - } - update = getLatestBeta(); - if (update != null && update.isActual()) { - update.versionName = "Latest beta: v" + update.versionName; - updates.add(update); - } - } - return updates.toArray(new AppUpdateInfo[updates.size()]); - } - - public AppUpdateInfo getLatestAny() { - AppUpdateInfo beta = getLatestBeta(); - AppUpdateInfo release = getLatestRelease(); - return beta != null && release != null && beta.versionCode > release.versionCode ? beta : release; - } - - @Nullable - public AppUpdateInfo getLatest(String branch) { - if (mResponse == null) { - return null; - } - try { - return new AppUpdateInfo(mResponse.getJSONObject(branch)); - } catch (Exception e) { - return null; - } - } - - @Nullable - public AppUpdateInfo getLatestRelease() { - return getLatest("release"); - } - - @Nullable - public AppUpdateInfo getLatestBeta() { - return getLatest("beta"); - } - - public static class AppUpdateInfo { - protected String versionName; - protected int versionCode; - protected String url; - - public String getVersionName() { - return versionName; - } - - public int getVersionCode() { - return versionCode; - } - - public String getUrl() { - return url; - } - - protected AppUpdateInfo(JSONObject jsonObject) throws JSONException { - this.versionName = jsonObject.getString("version_name"); - this.versionCode = jsonObject.getInt("version"); - this.url = jsonObject.getString("url"); - } - - public boolean isActual() { - return versionCode > BuildConfig.VERSION_CODE; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/BookmarksProvider.java b/app/src/main/java/org/nv95/openmanga/providers/BookmarksProvider.java deleted file mode 100644 index fb7d4636..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/BookmarksProvider.java +++ /dev/null @@ -1,142 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.helpers.StorageHelper; -import org.nv95.openmanga.items.Bookmark; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.items.ThumbSize; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.StorageUtils; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; - -/** - * Created by nv95 on 20.11.16. - */ - -public class BookmarksProvider { - - private static final String TABLE_NAME = "bookmarks"; - private static final String[] PROJECTION = new String[] { - "_id", "manga_id", "chapter", "page", "name", "thumbnail", "timestamp" - }; - - private static WeakReference instanceReference = new WeakReference<>(null); - - private final StorageHelper mStorageHelper; - private final Context mContext; - - private BookmarksProvider(Context context) { - mContext = context; - mStorageHelper = new StorageHelper(context); - } - - public static BookmarksProvider getInstance(Context context) { - BookmarksProvider instance = instanceReference.get(); - if (instance == null) { - instance = new BookmarksProvider(context); - instanceReference = new WeakReference<>(instance); - } - return instance; - } - - @Override - protected void finalize() throws Throwable { - mStorageHelper.close(); - super.finalize(); - } - - @NonNull - public Bookmark add(MangaSummary manga, int chapterNumber, int page, String thumbnail) { - Bookmark bookmark = new Bookmark(); - bookmark.mangaId = manga.id; - bookmark.chapter = chapterNumber; - bookmark.page = page; - bookmark.name = AppHelper.ellipsize(manga.name, 10); - bookmark.datetime = System.currentTimeMillis(); - File thumbDest = new File(mContext.getExternalFilesDir("thumbs"), String.valueOf(bookmark.hashCode())); - StorageUtils.copyThumbnail(thumbnail, thumbDest.getPath(), ThumbSize.THUMB_SIZE_SMALL); - bookmark.thumbnailFile = thumbDest.getPath(); - - SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - ContentValues cv = bookmark.toContentValues(); - int updCount = database.update(TABLE_NAME, cv, "_id=" + bookmark.hashCode(), null); - if (updCount == 0) { - database.insert(TABLE_NAME, null, cv); - } - return bookmark; - } - - public ArrayList getAll(int mangaId, int chapter) { - return listFromCursor( - mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, PROJECTION, "manga_id=? AND chapter=?", new String[]{String.valueOf(mangaId), String.valueOf(chapter)}, null, null, "timestamp DESC") - ); - } - - public ArrayList getAll(int mangaId) { - return listFromCursor( - mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, PROJECTION, "manga_id=?", new String[]{String.valueOf(mangaId)}, null, null, "timestamp DESC") - ); - } - - public ArrayList getAll() { - return listFromCursor( - mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, PROJECTION, null, null, null, null, "timestamp DESC") - ); - } - - @Nullable - public Bookmark get(int id) { - ArrayList list = listFromCursor( - mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, PROJECTION, "_id=?", new String[]{String.valueOf(id)}, null, null, "timestamp DESC") - ); - return list.isEmpty() ? null : list.get(0); - } - - public boolean remove(int id) { - return mStorageHelper.getWritableDatabase().delete(TABLE_NAME, "_id=?", new String[]{String.valueOf(id)}) > 0; - } - - public boolean remove(MangaSummary manga, int chapter, int page) { - return mStorageHelper.getWritableDatabase().delete(TABLE_NAME, "manga_id=? AND chapter=? AND page=?", new String[]{String.valueOf(manga.id), String.valueOf(chapter), String.valueOf(page)}) > 0; - } - - public int removeAll(int mangaId) { - return mStorageHelper.getWritableDatabase().delete(TABLE_NAME, "manga_id=?", new String[]{String.valueOf(mangaId)}); - } - - public void clear() { - mStorageHelper.getWritableDatabase().delete(TABLE_NAME, null, null); - } - - private static ArrayList listFromCursor(Cursor cursor) { - ArrayList list = new ArrayList<>(cursor.getCount()); - Bookmark bookmark; - if (cursor.moveToFirst()) { - do { - bookmark = new Bookmark(cursor.getInt(0)); - bookmark.mangaId = cursor.getInt(1); - bookmark.chapter = cursor.getInt(2); - bookmark.page = cursor.getInt(3); - bookmark.name = cursor.getString(4); - bookmark.thumbnailFile = cursor.getString(5); - bookmark.datetime = cursor.getLong(6); - list.add(bookmark); - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/DesuMeProvider.java b/app/src/main/java/org/nv95/openmanga/providers/DesuMeProvider.java deleted file mode 100644 index 077c889d..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/DesuMeProvider.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.AppHelper; - -import java.net.URLEncoder; -import java.util.ArrayList; - -/** - * Created by nv95 on 07.03.16. - */ -public class DesuMeProvider extends MangaProvider { - - protected static final int sorts[] = {R.string.sort_popular, R.string.sort_alphabetical, R.string.sort_updated}; - protected static final String sortUrls[] = {"popular", "name", "updated"}; - protected static final int genres[] = {R.string.genre_all, R.string.genre_action, R.string.genre_martialarts, R.string.genre_vampires, R.string.web, - R.string.genre_military, R.string.genre_harem, R.string.genre_youkai, R.string.genre_drama, R.string.genre_josei, R.string.genre_game, - R.string.genre_historical, R.string.genre_comedy, R.string.genre_magic, R.string.genre_mecha, R.string.genre_mystery, - R.string.genre_music, R.string.genre_sci_fi, R.string.genre_parodi, R.string.genre_slice_of_life, R.string.genre_police, - R.string.genre_adventure, R.string.genre_psychological, R.string.genre_romance, R.string.genre_samurai, R.string.genre_supernatural, - R.string.genre_genderbender, R.string.genre_sports, R.string.genre_superpower, R.string.genre_seinen, R.string.genre_shoujo, - R.string.genre_shounen, R.string.genre_shounen_ai, R.string.genre_thriller, R.string.genre_horror, R.string.genre_fantasy, - R.string.genre_hentai, R.string.genre_school, R.string.genre_ecchi, R.string.genre_yuri, R.string.genre_yaoi - }; - private static final String genreUrls[] = { - "action", "martial%20arts", "vampire", "web", "military", "harem", "demons", "drama", "josei", "game", - "historical", "comedy", "magic", "mecha", "mystery", "music", "sci-fi", "parody", "slice of life", - "police", "adventure", "psychological", "romance", "samurai", "supernatural", "gender%20bender", "sports", - "super%20power", "seinen", "shoujo", - "shounen", "shounen%20ai", "thriller", "horror", "fantasy", "hentai", "school", "ecchi", "yuri", "yaoi" - }; - - public DesuMeProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - JSONObject jo = new JSONObject(getRaw( - "http://desu.me/manga/api/?limit=20&order_by=" - + sortUrls[sort] - + "&page=" - + (page + 1) - + (genre == 0 ? "" : "&genres=" + genreUrls[genre - 1]) - )); - MangaInfo manga; - JSONArray ja = jo.getJSONArray("response"); - for (int i=0; i getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - JSONObject jo = new JSONObject(getRaw(readLink)).getJSONObject("response"); - JSONArray ja = jo.getJSONObject("pages").getJSONArray("list"); - MangaPage page; - for (int i = 0; i < ja.length(); i++) { - jo = ja.getJSONObject(i); - page = new MangaPage(jo.getString("img")); - page.provider = DesuMeProvider.class; - pages.add(page); - } - return pages; - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - MangaList list = new MangaList(); - JSONObject jo = new JSONObject(getRaw( - "http://desu.me/manga/api/?limit=20" - + "&page=" - + (page + 1) - + "&search=" + URLEncoder.encode(query, "UTF-8") - )); - MangaInfo manga; - JSONArray ja = jo.getJSONArray("response"); - for (int i=0; i 1 && res != 0 && "-1px".equals(a[1])) { - res += 10; - } - return res; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path, DEF_COOKIE); - Element body = document.body(); - StringBuilder builder = new StringBuilder(); - for (Element o : body.getElementById("taglist").select("tr")) { - builder.append(o.text()).append('\n'); - } - summary.description = builder.toString().trim(); - try { - String pvw = body.getElementById("gd1").child(0).attr("style"); - int p = pvw.indexOf("url(") + 4; - summary.preview = pvw.substring(p, pvw.indexOf(')', p)); - } catch (Exception ignored) { - } - Elements els = body.select("table.ptt").first().select("td"); - els.remove(els.size() - 1); - els.remove(0); - MangaChapter chapter; - for (Element o : els.select("a")) { - chapter = new MangaChapter(); - chapter.name = mangaInfo.name + " [" + o.text() + "]"; - chapter.readLink = concatUrl(mDomain, o.attr("href")); - chapter.provider = summary.provider; - summary.chapters.add(chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - String s; - MangaPage page; - try { - Document document = getPage(readLink, DEF_COOKIE); - Elements elements = document.body().select("div.gdtm"); - for (Element o : elements) { - s = o.select("a").first().attr("href"); - page = new MangaPage(concatUrl(mDomain, s)); - page.provider = EHentaiProvider.class; - pages.add(page); - } - } catch (Exception e) { - e.printStackTrace(); - } - return pages; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - try { - Document document = getPage(mangaPage.path, DEF_COOKIE); - return concatUrl(mDomain, document.body().select("img").get(4).attr("src")); - } catch (Exception e) { - return null; - } - } - - @Override - public String getName() { - return mDomain.charAt(8) == 'x' ? "ExHentai" : "E-Hentai"; - } - - @Override - public MangaList search(String query, int page) throws Exception { - MangaList list = new MangaList(); - Document document = getPage(mDomain + "?page=" + page + "&f_search=" + URLEncoder.encode(query, "UTF-8") + "&f_apply=Apply+Filter", DEF_COOKIE); - Element root = document.body().select("div.itg").first(); - MangaInfo manga; - Elements elements = root.select("div.id1"); - for (Element o : elements) { - manga = new MangaInfo(); - manga.name = o.select("a").first().text(); - manga.subtitle = getFromBrackets(manga.name); - manga.name = manga.name.replaceAll("\\[[^\\[,\\]]+]","").trim(); - manga.genres = ""; - manga.rating = parseRating(o.select("div.id43").first().attr("style")); - manga.path = concatUrl(mDomain, o.select("a").first().attr("href")); - manga.preview = o.select("img").first().attr("src"); - manga.provider = EHentaiProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Nullable - @Override - public String[] getGenresTitles(Context context) { - return AppHelper.getStringArray(context, genres); - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - private String getFromBrackets(String src) { - Matcher m = Pattern.compile("\\[[^\\[,\\]]+]").matcher(src); - StringBuilder sb = new StringBuilder(); - String t; - boolean firstTime = true; - while (m.find()) { - t = m.group(0); - t = t.substring(1, t.length() - 2); - if (firstTime) { - firstTime = false; - } else { - sb.append(", "); - } - sb.append(t); - } - return sb.toString(); - } - - private boolean auth() { - String login = getStringPreference("login", ""); - String password = getStringPreference("password", ""); - return !TextUtils.isEmpty(login) && !TextUtils.isEmpty(password) && auth(login, password, null); - } - - @Override - protected String getAuthCookie() { - if (!isAuthorized()) { - auth(); - } - return sAuthCookie; - } - - public static boolean isAuthorized() { - return !"".equals(sAuthCookie); - } - - @NonNull - public static String getCookie() { - return AppHelper.concatStr(sAuthCookie, DEF_COOKIE); - } - - @SuppressWarnings("WeakerAccess") - @WorkerThread - public static boolean auth(String login, String password, String arg3) { - CookieParser cp = NetworkUtils.authorize( - "https://forums.e-hentai.org/index.php?act=Login&CODE=01", - "referer", - "https://forums.e-hentai.org/index.php", - "UserName", - login, - "PassWord", - password, - "CookieDate", - "1" - ); - if (cp == null || TextUtils.isEmpty(cp.getValue("ipb_pass_hash"))) { - Log.d("AUTH", "fail"); - return false; - } else { - Log.d("AUTH", "OK"); - sAuthCookie = cp.toString(); - return true; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/FavouritesProvider.java b/app/src/main/java/org/nv95/openmanga/providers/FavouritesProvider.java deleted file mode 100755 index f4f6bd30..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/FavouritesProvider.java +++ /dev/null @@ -1,354 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.StorageHelper; -import org.nv95.openmanga.helpers.SyncHelper; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.services.SyncService; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.FileLogger; - -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Date; -import java.util.Map; - -import static org.nv95.openmanga.items.MangaInfo.STATUS_UNKNOWN; - -/** - * Created by nv95 on 03.10.15. - */ -public class FavouritesProvider extends MangaProvider { - - private static final String TABLE_NAME = "favourites"; - private static final int sorts[] = {R.string.sort_latest, R.string.sort_alphabetical}; - private static final String sortUrls[] = {"timestamp DESC", "name COLLATE NOCASE"}; - private static WeakReference instanceReference = new WeakReference<>(null); - private final StorageHelper mStorageHelper; - private final Context mContext; - - private FavouritesProvider(Context context) { - super(context); - mContext = context; - mStorageHelper = new StorageHelper(context); - } - - public static FavouritesProvider getInstance(Context context) { - FavouritesProvider instance = instanceReference.get(); - if (instance == null) { - instance = new FavouritesProvider(context); - instanceReference = new WeakReference<>(instance); - } - return instance; - } - - @Override - protected void finalize() throws Throwable { - mStorageHelper.close(); - super.finalize(); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws IOException { - if (page > 0) - return null; - final SQLiteDatabase database = mStorageHelper.getReadableDatabase(); - MangaList list = null; - MangaInfo manga; - Map updates = NewChaptersProvider.getInstance(mContext) - .getLastUpdates(); - //noinspection TryFinallyCanBeTryWithResources - try { - list = new MangaList(); - Cursor cursor = database.query(TABLE_NAME, new String[]{"id", "name", "subtitle", "summary", "preview", "path", "provider", "rating"}, - genre == 0 ? null : "category=" + genre, null, null, null, sortUrls[sort]); - if (cursor.moveToFirst()) { - do { - manga = new MangaInfo(); - manga.id = cursor.getInt(0); - manga.name = cursor.getString(1); - manga.subtitle = cursor.getString(2); - manga.genres = cursor.getString(3); - manga.preview = cursor.getString(4); - manga.path = cursor.getString(5); - try { - manga.provider = (Class) Class.forName(cursor.getString(6)); - } catch (ClassNotFoundException e) { - manga.provider = LocalMangaProvider.class; - } - manga.status = STATUS_UNKNOWN; - manga.rating = (byte) cursor.getInt(7); - manga.extra = updates.containsKey(manga.id) ? - "+" + updates.get(manga.id) : null; - list.add(manga); - } while (cursor.moveToNext()); - } - cursor.close(); - } catch (Exception e) { - FileLogger.getInstance().report("FAV", e); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - return null; - } - - @Override - public ArrayList getPages(String readLink) { - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return null; - } - - @Override - public String getName() { - return mContext.getString(R.string.action_favourites); - } - - @Deprecated - public boolean add(MangaInfo mangaInfo) { - return add(mangaInfo, 0); - } - - public boolean add(MangaInfo mangaInfo, int category) { - final ContentValues cv = new ContentValues(); - cv.put("id", mangaInfo.id); - cv.put("name", mangaInfo.name); - cv.put("subtitle", mangaInfo.subtitle); - cv.put("summary", mangaInfo.genres); - cv.put("preview", mangaInfo.preview); - cv.put("provider", mangaInfo.provider.getName()); - cv.put("path", mangaInfo.path); - cv.put("timestamp", new Date().getTime()); - cv.put("category", category); - cv.put("rating", mangaInfo.rating); - SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - boolean res = (database.insert(TABLE_NAME, null, cv) != -1); - if (res) { - SyncService.syncDelayed(mContext); - } - return res; - } - - public boolean remove(MangaInfo mangaInfo) { - final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - if (database.delete(TABLE_NAME, "id=?", new String[]{String.valueOf(mangaInfo.id)}) > 0) { - SyncHelper syncHelper = SyncHelper.get(mContext); - if (syncHelper.isFavouritesSyncEnabled()) { - syncHelper.setDeleted(database, TABLE_NAME, mangaInfo.id); - SyncService.syncDelayed(mContext); - } - return true; - } else { - return false; - } - } - - @Override - public boolean remove(long[] ids) { - final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - SyncHelper syncHelper = SyncHelper.get(mContext); - boolean syncEnabled = syncHelper.isFavouritesSyncEnabled(); - database.beginTransaction(); - for (long o : ids) { - database.delete(TABLE_NAME, "id=" + o, null); - if (syncEnabled) { - syncHelper.setDeleted(database, TABLE_NAME, o); - } - } - database.setTransactionSuccessful(); - database.endTransaction(); - if (syncEnabled) { - SyncService.syncDelayed(mContext); - } - return true; - } - - - public boolean has(MangaInfo mangaInfo) { - final SQLiteDatabase database = mStorageHelper.getReadableDatabase(); - return StorageHelper.getRowCount(database, TABLE_NAME, "id=" + mangaInfo.id) != 0; - } - - public int getCategory(MangaInfo mangaInfo) { - int res = -1; - Cursor cursor = null; - try { - cursor = mStorageHelper.getReadableDatabase().query(TABLE_NAME, new String[]{"category"}, "id=" + mangaInfo.id, null, null, null, null); - if (cursor.moveToFirst()) { - res = cursor.getInt(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return res; - } - - @Override - public String[] getSortTitles(Context context) { - return AppHelper.getStringArray(context, sorts); - } - - @Nullable - @Override - public String[] getGenresTitles(Context context) { - return (context.getString(R.string.genre_all) + "," + - PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()) - .getString("fav.categories", context.getString(R.string.favourites_categories_default))).split(",\\s*"); - } - - public void move(long[] ids, int category) { - SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put("category", category); - database.beginTransaction(); - for (long id : ids) { - database.update(TABLE_NAME, cv, "id=?", new String[]{String.valueOf(id)}); - } - database.setTransactionSuccessful(); - database.endTransaction(); - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isItemsRemovable() { - return true; - } - - @Override - public boolean isMultiPage() { - return false; - } - - public static void dialog(final Context context, @Nullable DialogInterface.OnClickListener doneListener, final MangaInfo mangaInfo) { - final int[] selected = new int[1]; - final DialogInterface.OnClickListener listener = doneListener; - CharSequence[] categories = (context.getString(R.string.category_no) + "," + - PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()) - .getString("fav.categories", context.getString(R.string.favourites_categories_default))) - .replaceAll(", ", ",").split(","); - AlertDialog.Builder builder = new AlertDialog.Builder(context) - .setTitle(R.string.action_favourites) - .setNegativeButton(android.R.string.cancel, null) - .setCancelable(true) - .setSingleChoiceItems(categories, 0, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - selected[0] = which; - } - }) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - getInstance(context).add(mangaInfo, selected[0]); - if (listener != null) { - listener.onClick(dialog, selected[0]); - } - } - }); - if (getInstance(context).has(mangaInfo)) { - builder.setNeutralButton(R.string.action_remove, doneListener); - } - builder.create().show(); - } - - @Nullable - public JSONArray dumps(long laterThen) { - Cursor cursor = null; - try { - JSONArray dump = new JSONArray(); - cursor = mStorageHelper.getReadableDatabase().query(TABLE_NAME, new String[]{ - "id", "name", "subtitle", "summary", "provider", "preview", "path", "timestamp", "rating" - }, "timestamp > ?", new String[]{String.valueOf(laterThen)}, null, null, null); - if (cursor.moveToFirst()) { - do { - JSONObject jobj = new JSONObject(); - JSONObject manga = new JSONObject(); - manga.put("id", cursor.getInt(0)); - manga.put("name", cursor.getString(1)); - manga.put("subtitle", AppHelper.strNotNull(cursor.getString(2))); - manga.put("summary", AppHelper.strNotNull(cursor.getString(3))); - manga.put("provider", cursor.getString(4)); - manga.put("preview", cursor.getString(5)); - manga.put("path", cursor.getString(6)); - manga.put("rating", cursor.getInt(8)); - jobj.put("manga", manga); - jobj.put("timestamp", cursor.getLong(7)); - dump.put(jobj); - } while (cursor.moveToNext()); - } - return dump; - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - public boolean inject(JSONArray jsonArray) { - SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - try { - int len = jsonArray.length(); - database.beginTransaction(); - for (int i=0;i getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - int start = 0; - String s; - Elements es = document.body().select("script"); - for (Element o : es) { - s = o.html(); - start = s.indexOf("fullimg\":["); - if (start != -1) { - start += 9; - int p = s.lastIndexOf("]") + 1; - s = s.substring(start, p); - JSONArray array = new JSONArray(s); - for (int i = 0; i < array.length() - 1; i++) { - page = new MangaPage(array.getString(i)); - page.provider = HentaichanProvider.class; - pages.add(page); - } - return pages; - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return mangaPage.path; - } - - @Override - public String getName() { - return "Хентай-тян"; - } - - @Override - public String[] getSortTitles(Context context) { - return super.getTitles(context, sorts); - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - final String urlRoot = "http://" + getStringPreference("domain", "hentai-chan.me") + "/"; - boolean byTag = query.startsWith(":"); - if (!byTag && page > 0) { - return null; - } - MangaList list = new MangaList(); - String url = urlRoot - + (byTag ? - "tags/" + URLEncoder.encode(query.substring(1), "UTF-8") + "&sort=manga?offset=" + (page * 20) - : "?do=search&subaction=search&story=" + URLEncoder.encode(query, "UTF-8")); - Document document = getPage(url); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.content_row"); - for (Element o : elements) { - manga = new MangaInfo(); - - t = o.select("h2").first(); - t = t.child(0); - manga.name = t.text(); - manga.path = concatUrl(urlRoot, t.attr("href")); - t = o.select("img").first(); - manga.preview = concatUrl(urlRoot, t.attr("src")); - t = o.select("div.genre").first(); - if (t != null) { - manga.genres = t.text(); - } - manga.provider = HentaichanProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - protected String getAuthCookie() { - if (sAuthCookie == null) { - sAuthCookie = ""; - String login = getStringPreference("login", ""); - String password = getStringPreference("password", ""); - if (!TextUtils.isEmpty(login) && !TextUtils.isEmpty(password)) { - auth(login, password, getStringPreference("domain", "hentai-chan.me")); - } - } - return sAuthCookie; - } - - @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"}) - public static boolean auth(String login, String password, String domain) { - final String urlRoot = "http://" + domain + "/"; - CookieParser cp = NetworkUtils.authorize( - urlRoot, - "login", - "submit", - "login_name", - login, - "login_password", - password, - "image", - "yay" - ); - if (cp == null || TextUtils.isEmpty(cp.getValue("dle_user_id")) || "deleted".equals(cp.getValue("dle_user_id"))) { - Log.d("AUTH", "fail"); - return false; - } else { - Log.d("AUTH", "OK"); - sAuthCookie = cp.toString(); - return true; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/HistoryProvider.java b/app/src/main/java/org/nv95/openmanga/providers/HistoryProvider.java deleted file mode 100755 index 72591370..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/HistoryProvider.java +++ /dev/null @@ -1,494 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.StorageHelper; -import org.nv95.openmanga.helpers.SyncHelper; -import org.nv95.openmanga.items.HistoryMangaInfo; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.staff.Languages; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.providers.staff.ProviderSummary; -import org.nv95.openmanga.services.SyncService; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.FileLogger; - -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Date; - -import static org.nv95.openmanga.items.MangaInfo.STATUS_UNKNOWN; - -/** - * Created by nv95 on 05.10.15. - */ -public class HistoryProvider extends MangaProvider { - - private static final String TABLE_NAME = "history"; - private static final int sorts[] = {R.string.sort_latest, R.string.sort_alphabetical}; - private static final String sortUrls[] = {"timestamp DESC", "name COLLATE NOCASE"}; - private static WeakReference instanceReference = new WeakReference<>(null); - private final StorageHelper mStorageHelper; - private final Context mContext; - - private HistoryProvider(Context context) { - super(context); - mContext = context; - mStorageHelper = new StorageHelper(context); - } - - public static HistoryProvider getInstance(Context context) { - HistoryProvider instance = instanceReference.get(); - if (instance == null) { - instance = new HistoryProvider(context); - instanceReference = new WeakReference<>(instance); - } - return instance; - } - - public HistoryMangaInfo getLast() { - Cursor cursor = null; - HistoryMangaInfo last = null; - try { - cursor = mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, new String[]{"id", "name", "subtitle", "summary", "preview", "path", "provider", "rating", "timestamp", "page", "chapter"}, null, null, null, null, sortUrls[0]); - if (cursor.moveToFirst()) { - last = new HistoryMangaInfo(); - last.id = cursor.getInt(0); - last.name = cursor.getString(1); - last.subtitle = cursor.getString(2); - last.genres = cursor.getString(3); - last.preview = cursor.getString(4); - last.path = cursor.getString(5); - try { - last.provider = (Class) Class.forName(cursor.getString(6)); - } catch (ClassNotFoundException e) { - last.provider = LocalMangaProvider.class; - } - last.rating = (byte) cursor.getInt(7); - last.status = STATUS_UNKNOWN; - last.extra = null; - last.timestamp = cursor.getLong(8); - last.page = cursor.getInt(9); - last.chapter = cursor.getInt(10); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return last; - } - - public MangaList getLast(int maxItems) { - MangaList list; - HistoryMangaInfo manga; - //noinspection TryFinallyCanBeTryWithResources - list = new MangaList(); - Cursor cursor = mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, new String[]{"id", "name", "subtitle", "summary", "preview", "path", "provider", "rating", "timestamp", "page", "chapter"}, - null, null, null, null, sortUrls[0], String.valueOf(maxItems)); - if (cursor.moveToFirst()) { - do { - manga = new HistoryMangaInfo(); - manga.id = cursor.getInt(0); - manga.name = cursor.getString(1); - manga.subtitle = cursor.getString(2); - manga.genres = cursor.getString(3); - manga.preview = cursor.getString(4); - manga.path = cursor.getString(5); - try { - manga.provider = (Class) Class.forName(cursor.getString(6)); - } catch (ClassNotFoundException e) { - manga.provider = LocalMangaProvider.class; - } - manga.rating = (byte) cursor.getInt(7); - manga.status = STATUS_UNKNOWN; - manga.extra = null; - manga.timestamp = cursor.getLong(8); - manga.page = cursor.getInt(9); - manga.chapter = cursor.getInt(10); - list.add(manga); - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } - - @Override - protected void finalize() throws Throwable { - mStorageHelper.close(); - super.finalize(); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws IOException { - if (page > 0) - return null; - MangaList list; - HistoryMangaInfo manga; - //noinspection TryFinallyCanBeTryWithResources - list = new MangaList(); - Cursor cursor = mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, new String[]{"id", "name", "subtitle", "summary", "preview", "path", "provider", "rating", "timestamp", "page", "chapter"}, - null, null, null, null, sortUrls[sort]); - if (cursor.moveToFirst()) { - do { - manga = new HistoryMangaInfo(); - manga.id = cursor.getInt(0); - manga.name = cursor.getString(1); - manga.subtitle = cursor.getString(2); - manga.genres = cursor.getString(3); - manga.preview = cursor.getString(4); - manga.path = cursor.getString(5); - try { - manga.provider = (Class) Class.forName(cursor.getString(6)); - } catch (ClassNotFoundException e) { - manga.provider = LocalMangaProvider.class; - } - manga.rating = (byte) cursor.getInt(7); - manga.status = STATUS_UNKNOWN; - manga.extra = null; - manga.timestamp = cursor.getLong(8); - manga.page = cursor.getInt(9); - manga.chapter = cursor.getInt(10); - list.add(manga); - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - return null; - } - - @Override - public ArrayList getPages(String readLink) { - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return null; - } - - @Override - public String getName() { - return mContext.getString(R.string.action_history); - } - - public boolean add(MangaInfo mangaInfo, int chapter, int page) { - final ContentValues cv = new ContentValues(); - cv.put("id", mangaInfo.id); - cv.put("name", mangaInfo.name); - cv.put("subtitle", mangaInfo.subtitle); - cv.put("summary", mangaInfo.genres); - cv.put("preview", mangaInfo.preview); - cv.put("provider", mangaInfo.provider.getName()); - cv.put("path", mangaInfo.path); - cv.put("timestamp", new Date().getTime()); - cv.put("chapter", chapter); - cv.put("page", page); - cv.put("rating", mangaInfo.rating); - final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - int updCount = database.update(TABLE_NAME, cv, "id=" + mangaInfo.id, null); - if (updCount == 0) { - database.insert(TABLE_NAME, null, cv); - } - SyncService.syncDelayed(mContext); - return true; - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) - return null; - MangaList list; - MangaInfo manga; - //noinspection TryFinallyCanBeTryWithResources - list = new MangaList(); - Cursor cursor = mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, new String[]{"id", "name", "subtitle", "summary", "preview", "path", "provider", "rating"}, - "name LIKE ? OR subtitle LIKE ?", new String[]{"%" + query + "%", "%" + query + "%"}, - null, null, sortUrls[0]); - if (cursor.moveToFirst()) { - do { - manga = new MangaInfo(); - manga.id = cursor.getInt(0); - manga.name = cursor.getString(1); - manga.subtitle = cursor.getString(2); - manga.genres = cursor.getString(3); - manga.preview = cursor.getString(4); - manga.path = cursor.getString(5); - try { - manga.provider = (Class) Class.forName(cursor.getString(6)); - } catch (ClassNotFoundException e) { - manga.provider = LocalMangaProvider.class; - } - manga.rating = (byte) cursor.getInt(7); - manga.status = STATUS_UNKNOWN; - manga.extra = null; - list.add(manga); - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - public boolean remove(MangaInfo mangaInfo) { - mStorageHelper.getWritableDatabase().delete(TABLE_NAME, "id=?", new String[]{String.valueOf(mangaInfo.id)}); - mStorageHelper.getWritableDatabase().delete("bookmarks", "manga_id=?", new String[]{String.valueOf(mangaInfo.id)}); - return true; - } - - @Override - public boolean remove(long[] ids) { - final SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - database.beginTransaction(); - SyncHelper syncHelper = SyncHelper.get(mContext); - boolean syncEnabled = syncHelper.isHistorySyncEnabled(); - for (long o : ids) { - database.delete(TABLE_NAME, "id=?", new String[]{String.valueOf(o)}); - database.delete("bookmarks", "manga_id=?", new String[]{String.valueOf(o)}); - if (syncEnabled) { - syncHelper.setDeleted(database, TABLE_NAME, o); - } - } - database.setTransactionSuccessful(); - database.endTransaction(); - if (syncEnabled) { - SyncService.syncDelayed(mContext); - } - return true; - } - - public boolean clear() { - SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - try { - database.beginTransaction(); - if (SyncHelper.get(mContext).isHistorySyncEnabled()) { - database.execSQL("INSERT INTO sync_delete (subject, manga_id, timestamp) SELECT 'history' AS subject, id AS manga_id, ? AS timestamp FROM history", - new String[]{String.valueOf(System.currentTimeMillis())}); - } - mStorageHelper.getWritableDatabase().delete(TABLE_NAME, null, null); - mStorageHelper.getWritableDatabase().delete(TABLE_NAME, null, null); - database.setTransactionSuccessful(); - return true; - } catch (Exception e) { - FileLogger.getInstance().report("HISTORY", e); - return false; - } finally { - database.endTransaction(); - } - } - - public boolean has(MangaInfo mangaInfo) { - boolean res; - SQLiteDatabase database = mStorageHelper.getReadableDatabase(); - res = StorageHelper.getRowCount(database, TABLE_NAME, "id=" + mangaInfo.id) != 0; - return res; - } - - @Nullable - public HistorySummary get(MangaInfo mangaInfo) { - HistorySummary res = null; - SQLiteDatabase database = mStorageHelper.getReadableDatabase(); - Cursor c = database.query(TABLE_NAME, new String[]{"timestamp", "chapter", "page"}, "id=" + mangaInfo.id, null, null, null, null); - if (c.moveToFirst()) { - res = new HistorySummary(); - res.time = c.getLong(0); - res.chapter = c.getInt(1); - res.page = c.getInt(2); - } - c.close(); - return res; - } - - @Nullable - public MangaInfo get(int id) { - MangaInfo manga = null; - Cursor cursor = mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, new String[]{"id", "name", "subtitle", "summary", "preview", "path", "provider", "rating"}, - "id=?", new String[]{String.valueOf(id)}, null, null, sortUrls[0]); - if (cursor.moveToFirst()) { - manga = new MangaInfo(); - manga.id = cursor.getInt(0); - manga.name = cursor.getString(1); - manga.subtitle = cursor.getString(2); - manga.genres = cursor.getString(3); - manga.preview = cursor.getString(4); - manga.path = cursor.getString(5); - try { - manga.provider = (Class) Class.forName(cursor.getString(6)); - } catch (ClassNotFoundException e) { - manga.provider = LocalMangaProvider.class; - } - manga.rating = (byte) cursor.getInt(7); - manga.status = STATUS_UNKNOWN; - manga.extra = null; - } - cursor.close(); - return manga; - } - - public boolean isWebMode(MangaInfo manga) { - boolean res = false; - SQLiteDatabase database = mStorageHelper.getReadableDatabase(); - Cursor c = database.query(TABLE_NAME, new String[]{"isweb"}, "id=" + manga.id, null, null, null, null); - if (c.moveToFirst()) { - res = c.getInt(0) != 0; - } - c.close(); - return res; - } - - public void setWebMode(MangaInfo manga, boolean isWeb) { - final ContentValues cv = new ContentValues(); - cv.put("isweb", isWeb); - mStorageHelper.getWritableDatabase().update(TABLE_NAME, cv, "id=" + manga.id, null); - } - - public static class HistorySummary { - protected int chapter; - protected int page; - protected long time; - - public int getChapter() { - return chapter; - } - - public int getPage() { - return page; - } - - public long getTime() { - return time; - } - } - - @Override - public String[] getSortTitles(Context context) { - return AppHelper.getStringArray(context, sorts); - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isItemsRemovable() { - return true; - } - - @Override - public boolean isMultiPage() { - return false; - } - - public static ProviderSummary getProviderSummary(Context context) { - return new ProviderSummary( - MangaProviderManager.PROVIDER_HISTORY, - context.getString(R.string.action_history), - HistoryProvider.class, - Languages.MULTI, - 0 - ); - } - - @Nullable - public JSONArray dumps(long laterThen) { - Cursor cursor = null; - try { - JSONArray dump = new JSONArray(); - cursor = mStorageHelper.getReadableDatabase().query(TABLE_NAME, new String[]{ - "id", "name", "subtitle", "summary", "provider", "preview", "path", "timestamp", "size", "chapter", "page", "isweb", "rating" - }, "timestamp > ?", new String[]{String.valueOf(laterThen)}, null, null, null); - if (cursor.moveToFirst()) { - do { - JSONObject jobj = new JSONObject(); - JSONObject manga = new JSONObject(); - manga.put("id", cursor.getInt(0)); - manga.put("name", cursor.getString(1)); - manga.put("subtitle", AppHelper.strNotNull(cursor.getString(2))); - manga.put("summary", AppHelper.strNotNull(cursor.getString(3))); - manga.put("provider", cursor.getString(4)); - manga.put("preview", cursor.getString(5)); - manga.put("path", cursor.getString(6)); - manga.put("rating", cursor.getInt(12)); - jobj.put("manga", manga); - jobj.put("timestamp", cursor.getLong(7)); - jobj.put("size", cursor.getInt(8)); - jobj.put("chapter", cursor.getInt(9)); - jobj.put("page", cursor.getInt(10)); - jobj.put("isweb", cursor.getInt(11)); - dump.put(jobj); - } while (cursor.moveToNext()); - } - return dump; - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - public boolean inject(JSONArray jsonArray) { - SQLiteDatabase database = mStorageHelper.getWritableDatabase(); - try { - int len = jsonArray.length(); - database.beginTransaction(); - for (int i=0;i instanceReference = new WeakReference<>(null); - private final Context mContext; - private final MangaStore mStore; - - public LocalMangaProvider(Context context) { - super(context); - mContext = context; - mStore = new MangaStore(context); - } - - public static LocalMangaProvider getInstance(Context context) { - LocalMangaProvider instance = instanceReference.get(); - if (instance == null) { - instance = new LocalMangaProvider(context); - instanceReference = new WeakReference<>(instance); - } - return instance; - } - - @Override - public MangaList getList(int page, int sort, int genre) { - if (page > 0) - return null; - MangaList list; - MangaInfo manga; - list = new MangaList(); - Cursor cursor = mStore.getDatabase(false) - .query(MangaStore.TABLE_MANGAS, new String[]{"id", "name", "subtitle", "summary", "dir", "source", "rating"}, null, null, null, null, sortUrls[sort]); - if (cursor.moveToFirst()) { - do { - manga = new MangaInfo(); - manga.id = cursor.getInt(0); - manga.name = cursor.getString(1); - manga.subtitle = cursor.getString(2); - manga.genres = cursor.getString(3); - manga.path = cursor.getString(4); - manga.preview = manga.path + "/cover"; - manga.provider = LocalMangaProvider.class; - manga.status = cursor.getString(5) == null ? MangaInfo.STATUS_UNKNOWN : MangaInfo.STATUS_ONGOING; - manga.rating = (byte) cursor.getInt(6); - list.add(manga); - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } - - public int getCount() { - Cursor cursor = null; - int res = 0; - try { - cursor = mStore.getDatabase(false) - .query(MangaStore.TABLE_MANGAS, null, null, null, null, null, null); - res = cursor.getCount(); - } catch (Exception e) { - res = -1; - } finally { - if (cursor != null) { - cursor.close(); - } - } - return res; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - MangaSummary summary = new MangaSummary(mangaInfo); - SQLiteDatabase database = mStore.getDatabase(false); - ChaptersList list = new ChaptersList(); - MangaChapter chapter; - Cursor cursor = database.query(MangaStore.TABLE_MANGAS, new String[]{"description", "source"}, "id=" + mangaInfo.id, null, null, null, null); - if (cursor.moveToFirst()) { - summary.description = cursor.getString(0); - summary.status = cursor.getString(1) == null ? MangaInfo.STATUS_UNKNOWN : MangaInfo.STATUS_ONGOING; - } - cursor.close(); - cursor = database.query(MangaStore.TABLE_CHAPTERS, new String[]{"id", "name", "number"}, "mangaid=" + mangaInfo.id, null, null, null, "number"); - if (cursor.moveToFirst()) { - do { - chapter = new MangaChapter(); - chapter.id = cursor.getInt(0); - chapter.name = cursor.getString(1); - chapter.number = cursor.getInt(2); - chapter.readLink = String.valueOf(chapter.id) + "\n" + String.valueOf(mangaInfo.id) + "\n" + mangaInfo.path; - chapter.provider = LocalMangaProvider.class; - list.add(chapter); - } while (cursor.moveToNext()); - } - cursor.close(); - summary.chapters = list; - return summary; - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList list = new ArrayList<>(); - MangaPage page; - final String[] data = readLink.split("\n"); - final String dir = data[2] + "/"; - - Cursor cursor = mStore.getDatabase(false) - .query(TABLE_PAGES, new String[]{"id", "file"}, "chapterid=? AND mangaid=?", new String[]{data[0], data[1]}, null, null, "number"); - if (cursor.moveToFirst()) { - do { - page = new MangaPage(); - page.id = cursor.getInt(0); - page.path = dir + cursor.getString(1); - page.provider = LocalMangaProvider.class; - list.add(page); - } while (cursor.moveToNext()); - } - cursor.close(); - - return list; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return mangaPage.path; - } - - @Override - public String getName() { - return mContext.getString(R.string.local_storage); - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isItemsRemovable() { - return true; - } - - @Override - public boolean isMultiPage() { - return false; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - public boolean remove(long[] ids) { - return mStore.dropMangas(ids) && HistoryProvider.getInstance(mContext).remove(ids); - } - - @Override - public String[] getSortTitles(Context context) { - return AppHelper.getStringArray(context, sorts); - } - - @WorkerThread - @Nullable - public MangaSummary getSource(MangaInfo manga) { - Cursor cursor = null; - try { - cursor = mStore.getDatabase(false) - .query(MangaStore.TABLE_MANGAS, new String[]{"provider", "source"}, "id=?", new String[]{String.valueOf(manga.id)}, null, null, null); - if (cursor.moveToFirst()) { - String providerName = cursor.getString(0); - if (providerName != null && providerName.length() != 0) { - MangaProvider provider = MangaProviderManager.instanceProvider(mContext, providerName); - if (provider != null) { - String link = cursor.getString(1); - MangaInfo mi = new MangaInfo(); - mi.name = manga.name; - mi.provider = provider.getClass(); - mi.preview = manga.preview; - mi.subtitle = manga.subtitle; - mi.id = manga.id; - mi.status = manga.status; - mi.genres = manga.genres; - mi.rating = manga.rating; - mi.path = link; - return provider.getDetailedInfo(mi); - } - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - @Nullable - public String getSourceUrl(int mangaId) { - Cursor cursor = null; - try { - cursor = mStore.getDatabase(false) - .query(MangaStore.TABLE_MANGAS, new String[]{"source"}, "id=?", new String[]{String.valueOf(mangaId)}, null, null, null); - if (cursor.moveToFirst()) { - return cursor.getString(0); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - public long[] getAllIds() { - ArrayList ids = new ArrayList<>(); - Cursor cursor = null; - try { - cursor = mStore.getDatabase(false) - .query(MangaStore.TABLE_MANGAS, new String[]{"id"}, null, null, null, null, null); - if (cursor.moveToFirst()) { - do { - ids.add(cursor.getLong(0)); - } while (cursor.moveToNext()); - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - long[] ids_a = new long[ids.size()]; - for (int i = 0; i < ids.size(); i++) { - ids_a[i] = ids.get(i); - } - return ids_a; - } - - @WorkerThread - public LocalMangaInfo[] getLocalInfo(long[] ids) { - LocalMangaInfo[] infos = new LocalMangaInfo[ids.length]; - Cursor cursor = null; - try { - for (int i = 0; i < ids.length; i++) { - cursor = mStore.getDatabase(false) - .query(MangaStore.TABLE_MANGAS, new String[]{"name", "dir"}, "id=?", new String[]{String.valueOf(ids[i])}, null, null, null); - if (cursor.moveToFirst()) { - infos[i] = new LocalMangaInfo(); - infos[i].id = ids[i]; - infos[i].name = cursor.getString(0); - infos[i].path = cursor.getString(1); - infos[i].size = StorageUtils.dirSize(new File(infos[i].path)); - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return infos; - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) - return null; - MangaList list; - MangaInfo manga; - list = new MangaList(); - Cursor cursor = mStore.getDatabase(false) - .query(MangaStore.TABLE_MANGAS, new String[]{"id", "name", "subtitle", "summary", "dir", "source", "rating"}, - "name LIKE ? OR subtitle LIKE ?", new String[]{"%" + query + "%", "%" + query + "%"}, - null, null, sortUrls[0]); - if (cursor.moveToFirst()) { - do { - manga = new MangaInfo(); - manga.id = cursor.getInt(0); - manga.name = cursor.getString(1); - manga.subtitle = cursor.getString(2); - manga.genres = cursor.getString(3); - manga.path = cursor.getString(4); - manga.preview = manga.path + "/cover"; - manga.provider = LocalMangaProvider.class; - manga.status = cursor.getString(5) == null ? MangaInfo.STATUS_UNKNOWN : MangaInfo.STATUS_ONGOING; - manga.rating = (byte) cursor.getInt(6); - list.add(manga); - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } - - public static ProviderSummary getProviderSummary(Context context) { - return new ProviderSummary( - MangaProviderManager.PROVIDER_LOCAL, - context.getString(R.string.local_storage), - LocalMangaProvider.class, - Languages.MULTI, - 0 - ); - } - - public boolean has(MangaInfo mangaInfo) { - return StorageHelper.getRowCount( - mStore.getDatabase(false), - MangaStore.TABLE_MANGAS, - "id=" + mangaInfo.id - ) != 0; - } - - public MangaSummary getLocalManga(MangaInfo manga) { - MangaSummary res = new MangaSummary(manga); - if (!has(manga)) { - return res; - } - res.provider = LocalMangaProvider.class; - File root = MangaStore.getMangaDir(mContext, mStore.getDatabase(false), manga.id); - res.path = root.getPath(); - res.preview = new File(root, "cover").getPath(); - return res; - } - - public ArrayList getLocalChaptersNumbers(int mangaId) { - SQLiteDatabase database = mStore.getDatabase(false); - ArrayList numbers = new ArrayList<>(); - Cursor cursor = database.query(MangaStore.TABLE_CHAPTERS, new String[]{"number"}, "mangaid=" + mangaId, null, null, null, "number"); - if (cursor.moveToFirst()) { - do { - numbers.add(cursor.getInt(0)); - } while (cursor.moveToNext()); - } - cursor.close(); - return numbers; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/MangaFoxProvider.java b/app/src/main/java/org/nv95/openmanga/providers/MangaFoxProvider.java deleted file mode 100644 index b85d88e8..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/MangaFoxProvider.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.text.Html; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; - -import java.net.URLEncoder; -import java.util.ArrayList; - -/** - * Created by nv95 on 04.02.16. - */ -public class MangaFoxProvider extends MangaProvider { - - private static final int sorts[] = {R.string.sort_alphabetical, R.string.sort_popular, R.string.sort_rating, R.string.sort_updated}; - private static final String sortUrls[] = {"?az", "", "?rating", "?latest"}; - private static final int genres[] = {R.string.genre_all, R.string.genre_action, R.string.genre_adult, - R.string.genre_adventure, R.string.genre_comedy, R.string.genre_doujinshi, R.string.genre_drama, - R.string.genre_ecchi, R.string.genre_fantasy, R.string.genre_genderbender, R.string.genre_harem, - R.string.genre_historical, R.string.genre_horror, R.string.genre_josei, R.string.genre_martialarts, - R.string.genre_mature, R.string.genre_mecha, R.string.genre_mystery, R.string.genre_oneshot, - R.string.genre_psychological, R.string.genre_romance, R.string.genre_school, R.string.genre_sci_fi, - R.string.genre_seinen, R.string.genre_shoujo, R.string.genre_shoujo_ai, R.string.genre_shounen, R.string.genre_shounen_ai, - R.string.genre_slice_of_life, R.string.genre_smut, R.string.genre_sports, R.string.genre_supernatural, - R.string.genre_tragedy, R.string.web, R.string.genre_yaoi, R.string.genre_yuri - }; - private static final String genreUrls[] = {"action", "adult", "adventure", "comedy", "doujinshi", - "drama", "ecchi", "fantasy", "gender-bender", "harem", "historical", "horror", "josei", "martial-arts", - "mature", "mecha", "mystery", "one-shot", "psychological", "romance", "school-life", "sci-fi", - "seinen", "shoujo", "shoujo-ai", "shounen", "shounen-ai", "slice-of-life", "smut", "sports", - "supernatural", "tragedy", "webtoons", "yaoi", "yuri" - }; - - public MangaFoxProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://mangafox.me/directory/" - + (genre == 0 ? "" : genreUrls[genre - 1] + "/") - + (page + 1) + ".htm" + sortUrls[sort]); - MangaInfo manga; - Element root = document.body().getElementById("mangalist").select("ul.list").first(); - for (Element o : root.select("li")) { - manga = new MangaInfo(); - manga.name = o.select("a.title").first().text(); - manga.subtitle = null; - try { - manga.genres = o.select("p.info").first().attr("title"); - } catch (Exception e) { - manga.genres = ""; - } - manga.path = "http:" + o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - manga.rating = (byte) (Byte.parseByte(o.select("span.rate").first().text().substring(0,3).replace(".","")) * 2); - manga.provider = MangaFoxProvider.class; - if (!o.select("em.tag_completed").isEmpty()) { - manga.status = MangaInfo.STATUS_COMPLETED; - } - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - summary.description = Html.fromHtml(e.select("p.summary").html()).toString().trim(); - summary.preview = e.select("div.cover").first().select("img").first().attr("src"); - MangaChapter chapter; - e = e.getElementById("chapters"); - for (Element o : e.select("a.tips")) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = "http:" + o.attr("href"); - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - String prefix = readLink.substring(0, readLink.lastIndexOf('/') + 1); - Element e = document.body().select("select.m").first(); - for (Element o : e.select("option")) { - page = new MangaPage(prefix + o.attr("value") + ".htm"); - page.provider = MangaTownProvider.class; - pages.add(page); - } - if (pages.size() != 0) { - pages.remove(pages.size() - 1); - } - return pages; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - public String getPageImage(MangaPage mangaPage) { - try { - Document document = getPage(mangaPage.path); - return document.body().getElementById("image").attr("src"); - } catch (Exception e) { - return null; - } - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return MangaList.empty(); - } - MangaList list = new MangaList(); - Document document = getPage("http://m.mangafox.me/search?k=" + URLEncoder.encode(query, "UTF-8")); - MangaInfo manga; - Element r; - String s; - Elements elements = document.body().select("ul.post-list").select("li"); - for (Element o : elements) { - manga = new MangaInfo(); - r = o.select("div.cover-info").first(); - manga.path = o.select("a").first().attr("href"); - manga.path = manga.path.replace("http://m.","http://"); - manga.path = concatUrl("http://mangafox.me/", manga.path); - manga.name = r.child(0).text(); - manga.genres = r.child(1).text(); - s = r.child(2).text().toLowerCase(); - if (s.contains("ongoing")) { - manga.status = MangaInfo.STATUS_ONGOING; - } else if (s.contains("complete")) { - manga.status = MangaInfo.STATUS_COMPLETED; - } - r = o.select("img").first(); - manga.preview = r.attr("src"); - manga.subtitle = r.attr("title"); - manga.provider = MangaFoxProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public String getName() { - return "MangaFox"; - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - public String[] getSortTitles(Context context) { - return super.getTitles(context, sorts); - } - - @Nullable - @Override - public String[] getGenresTitles(Context context) { - return super.getTitles(context, genres); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/MangaProvider.java b/app/src/main/java/org/nv95/openmanga/providers/MangaProvider.java deleted file mode 100755 index 46a4fd54..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/MangaProvider.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.jsoup.nodes.Document; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.NetworkUtils; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * Created by nv95 on 30.09.15. - */ -public abstract class MangaProvider { - - protected boolean features[]; - private final SharedPreferences mPrefs; - - protected final Document getPage(String url) throws IOException { - return NetworkUtils.httpGet(url, getAuthCookie()); - } - - protected final Document getPage(String url, @NonNull String cookie) throws IOException { - return NetworkUtils.httpGet(url, AppHelper.concatStr(getAuthCookie(), cookie)); - } - - protected final Document postPage(String url, String... data) throws IOException { - return NetworkUtils.httpPost(url, getAuthCookie(), data); - } - - @NonNull - protected final String getRaw(String url) throws IOException { - return NetworkUtils.getRaw(url, getAuthCookie()); - } - - public MangaProvider(Context context) { - mPrefs = context.getSharedPreferences("prov_" + this.getClass().getSimpleName(), Context.MODE_PRIVATE); - } - - @NonNull - protected final String getStringPreference(@NonNull String key, @NonNull String defValue) { - return mPrefs.getString(key, defValue); - } - - protected final boolean getBooleanPreference(@NonNull String key, boolean defValue) { - return mPrefs.getBoolean(key, defValue); - } - - protected final int getIntPreference(@NonNull String key, int defValue) { - return mPrefs.getInt(key, defValue); - } - - //content access methods - public abstract MangaList getList(int page, int sort, int genre) throws Exception; - - @Deprecated - public MangaList getList(int page, int sort) throws Exception { - return getList(page, sort, 0); - } - - @Deprecated - public MangaList getList(int page) throws Exception { - return getList(page, 0, 0); - } - - public abstract MangaSummary getDetailedInfo(MangaInfo mangaInfo); - - public abstract ArrayList getPages(String readLink); - - public abstract String getPageImage(MangaPage mangaPage); - - //optional content acces methods - @Nullable - public MangaList search(String query, int page) throws Exception { - return null; - } - - public boolean remove(long[] ids) { - return false; - } - - //other methods - public abstract String getName(); - - @Nullable - public String[] getSortTitles(Context context) { - return null; - } - - @Nullable - public String[] getGenresTitles(Context context) { - return null; - } - - @Deprecated - final String[] getTitles(Context context, int[] ids) { - return AppHelper.getStringArray(context, ids); - } - - public boolean hasGenres() { - return false; - } - - public boolean hasSort() { - return false; - } - - public boolean isItemsRemovable() { - return false; - } - - public boolean isSearchAvailable() { - return false; - } - - public boolean isMultiPage() { - return true; - } - - @Nullable - protected String getAuthCookie() { - return null; - } - - static String concatUrl(String root, String url) { - return url == null || url.startsWith("http://") || url.startsWith("https://") ? url : root + (url.startsWith("/") ? url.substring(1) : url); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/MangaReaderProvider.java b/app/src/main/java/org/nv95/openmanga/providers/MangaReaderProvider.java deleted file mode 100755 index eb5c59b1..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/MangaReaderProvider.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.text.Html; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; - -import java.net.URLEncoder; -import java.util.ArrayList; - -/** - * Created by nv95 on 14.10.15. - */ -public class MangaReaderProvider extends MangaProvider { - - protected static final int sorts[] = {R.string.sort_popular}; - protected static final String sortUrls[] = {""}; - - public MangaReaderProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://www.mangareader.net/popular/" + page * 30); - MangaInfo manga; - Elements elements = document.body().select("div.mangaresultinner"); - for (Element o : elements) { - manga = new MangaInfo(); - manga.name = o.select("h3").first().text(); - try { - manga.subtitle = o.select("div.chapter_count").first().text(); - } catch (Exception e) { - manga.subtitle = ""; - } - manga.genres = o.select("div.manga_genre").first().text(); - manga.path = "http://www.mangareader.net" + o.select("a").first().attr("href"); - manga.preview = o.select("div.imgsearchresults").first().attr("style"); - manga.preview = manga.preview.substring(manga.preview.indexOf('\'') + 1, manga.preview.lastIndexOf('\'')); - manga.provider = MangaReaderProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - String descr = e.select("table").first().html(); - int p = descr.indexOf(">Tweet"); - if (p > 0) - descr = descr.substring(0, p); - summary.description = Html.fromHtml(descr).toString().trim(); - summary.preview = e.getElementById("mangaimg").child(0).attr("src"); - MangaChapter chapter; - e = e.getElementById("listing"); - for (Element o : e.select("a")) { - chapter = new MangaChapter(); - chapter.name = o.text() + o.parent().ownText(); - chapter.readLink = concatUrl("http://www.mangareader.net/", o.attr("href")); - chapter.provider = summary.provider; - summary.chapters.add(chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - Element e = document.body().getElementById("selectpage"); - for (Element o : e.select("option")) { - page = new MangaPage("http://www.mangareader.net" + o.attr("value")); - page.provider = MangaReaderProvider.class; - pages.add(page); - } - } catch (Exception e) { - e.printStackTrace(); - } - return pages; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - try { - Document document = getPage(mangaPage.path); - return document.body().getElementById("img").attr("src"); - } catch (Exception e) { - return null; - } - } - - @Override - public String getName() { - return "MangaReader"; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - //advanced-------- - - - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return MangaList.empty(); - } - MangaList list = new MangaList(); - Document document = getPage("http://www.mangareader.net/search/?w=" + URLEncoder.encode(query, "UTF-8")); - MangaInfo manga; - Elements elements = document.body().select("div.mangaresultinner"); - for (Element o : elements) { - manga = new MangaInfo(); - manga.name = o.select("h3").first().text(); - try { - manga.subtitle = o.select("div.chapter_count").first().text(); - } catch (Exception e) { - manga.subtitle = ""; - } - manga.genres = o.select("div.manga_genre").first().text(); - manga.path = concatUrl("http://www.mangareader.net/", o.select("a").first().attr("href")); - manga.preview = o.select("div.imgsearchresults").first().attr("style"); - manga.preview = manga.preview.substring(manga.preview.indexOf('\'') + 1, manga.preview.lastIndexOf('\'')); - manga.provider = MangaReaderProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public String[] getSortTitles(Context context) { - return super.getTitles(context, sorts); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/providers/MangaTownProvider.java b/app/src/main/java/org/nv95/openmanga/providers/MangaTownProvider.java deleted file mode 100755 index 09720fea..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/MangaTownProvider.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.text.Html; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.AppHelper; - -import java.net.URLEncoder; -import java.util.ArrayList; - -/** - * Created by nv95 on 06.10.15. - */ -public class MangaTownProvider extends MangaProvider { - - protected static final int sorts[] = {R.string.sort_latest, R.string.sort_popular}; - protected static final String sortUrls[] = {"latest", "hot"}; - protected static final int genres[] = {R.string.genre_all, R.string.genre_romance, R.string.genre_adventure, R.string.genre_school, R.string.genre_comedy, R.string.genre_vampires, R.string.genre_youkai, R.string.genre_horror, R.string.genre_genderch, R.string.genre_harem, R.string.genre_ecchi, R.string.genre_shoujo, R.string.genre_seinen, R.string.genre_shounen, R.string.genre_yaoi}; - protected static final String genreUrls[] = {"romance", "adventure", "school_life", "comedy", "vampire", "youkai", "horror", "gender_bender", "harem", "ecchi", "shoujo", "seinen", "shounen", "yaoi"}; - - public MangaTownProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://www.mangatown.com/" + sortUrls[sort] + "/" - + (genre == 0 ? "" : genreUrls[genre - 1] + "/") - + (page + 1) + ".htm"); - MangaInfo manga; - Element root = document.body().select("ul.post-list").first(); - for (Element o : root.select("li")) { - manga = new MangaInfo(); - manga.name = o.select("p.title").first().text(); - manga.subtitle = ""; - try { - manga.genres = o.select("p").get(1).text(); - } catch (Exception e) { - manga.genres = ""; - } - manga.path = o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - manga.provider = MangaTownProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - summary.description = Html.fromHtml(e.getElementById("show").html()).toString().trim(); - summary.preview = e.select("img").first().attr("src"); - - MangaChapter chapter; - e = e.select("ul.detail-ch-list").first(); - for (Element o : e.select("li")) { - chapter = new MangaChapter(); - chapter.name = o.select("a").first().text() + " " + o.select("span").get(0).text(); - chapter.readLink = o.select("a").first().attr("href"); - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - //genres.addDefaultChapter(); - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - Element e = document.body().select("select").get(1); - for (Element o : e.select("option")) { - page = new MangaPage(o.attr("value")); - page.provider = MangaTownProvider.class; - pages.add(page); - } - return pages; - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - try { - Document document = getPage(mangaPage.path); - return document.body().getElementById("image").attr("src"); - } catch (Exception e) { - return null; - } - } - - @Override - public MangaList search(String query, int page) throws Exception { - MangaList list = new MangaList(); - Document document = - getPage("http://www.mangatown.com/search/" + page + ".html?name=" - + URLEncoder.encode(query, "UTF-8")); - MangaInfo manga; - Element ul = document.body().select("ul.post-list").first(); - for (Element o : ul.select("li")) - { - manga = new MangaInfo(); - manga.name = o.select("a").first().attr("rel"); - manga.subtitle = ""; - manga.path = o.select("a").first().attr("href"); - try { - manga.genres = o.select("p").get(1).text(); - } catch (Exception e) { - manga.genres = ""; - } - - manga.provider = MangaTownProvider.class; - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - - manga.path.hashCode(); - - list.add(manga); - } - return list; - } - - @Override - public String getName() { - return "MangaTown"; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean isSearchAvailable() { return true; } - - @Override - public String[] getSortTitles(Context context) { - return AppHelper.getStringArray(context, sorts); - } - - @Override - public String[] getGenresTitles(Context context) { - return AppHelper.getStringArray(context, genres); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/MangachanProvider.java b/app/src/main/java/org/nv95/openmanga/providers/MangachanProvider.java deleted file mode 100755 index a6733be9..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/MangachanProvider.java +++ /dev/null @@ -1,244 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.util.Log; - -import org.json.JSONArray; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.AppHelper; -import org.nv95.openmanga.utils.CookieParser; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.NetworkUtils; - -import java.util.ArrayList; - -/** - * Created by nv95 on 14.12.15. - */ -public class MangachanProvider extends MangaProvider { - - protected static final int sorts[] = {R.string.sort_latest, R.string.sort_popular, R.string.sort_alphabetical}; - protected static final String sortUrls[] = {"datedesc", "favdesc", "abcasc"}; - - protected static final int genres[] = { - R.string.genre_all, R.string.genre_art, R.string.genre_martialarts, - R.string.genre_vampires, R.string.genre_webtoon, R.string.genre_harem, - R.string.genre_doujinshi, R.string.genre_drama, R.string.genre_mecha, - R.string.genre_slice_of_life, R.string.genre_shoujo, - R.string.genre_shoujo_ai, R.string.genre_shounen, R.string.genre_shounen_ai, - R.string.genre_tragedy - }; - private static final String genreUrls[] = { - "%D0%B0%D1%80%D1%82", "%D0%B1%D0%BE%D0%B5%D0%B2%D1%8B%D0%B5_%D0%B8%D1%81%D0%BA%D1%83%D1%81%D1%81%D1%82%D0%B2%D0%B0", - "%D0%B2%D0%B0%D0%BC%D0%BF%D0%B8%D1%80%D1%8B", "%D0%B2%D0%B5%D0%B1", "%D0%B3%D0%B0%D1%80%D0%B5%D0%BC", - "%D0%B4%D0%BE%D0%B4%D0%B7%D0%B8%D0%BD%D1%81%D0%B8", "%D0%B4%D1%80%D0%B0%D0%BC%D0%B0", "%D0%BC%D0%B5%D1%85%D0%B0", - "%D0%BF%D0%BE%D0%B2%D1%81%D0%B5%D0%B4%D0%BD%D0%B5%D0%B2%D0%BD%D0%BE%D1%81%D1%82%D1%8C", "%D1%81%D1%91%D0%B4%D0%B7%D1%91", - "%D1%81%D1%91%D0%B4%D0%B7%D1%91-%D0%B0%D0%B9", "%D1%81%D1%91%D0%BD%D1%8D%D0%BD", "%D1%81%D1%91%D0%BD%D1%8D%D0%BD-%D0%B0%D0%B9", - "%D1%82%D1%80%D0%B0%D0%B3%D0%B5%D0%B4%D0%B8%D1%8F" - }; - - private static String sAuthCookie = null; - - public MangachanProvider(Context context) { - super(context); - if ("".equals(sAuthCookie)) { - sAuthCookie = null; - } - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://mangachan.me/" + (genre == 0 ? "manga/new" : "tags/" + genreUrls[genre-1]) + "&n=" + sortUrls[sort] + "?offset=" + page * 20); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.content_row"); - for (Element o : elements) { - manga = new MangaInfo(); - - t = o.select("h2").first(); - t = t.child(0); - manga.name = t.text(); - manga.path = concatUrl("http://mangachan.me/", t.attr("href")); - t = o.select("img").first(); - manga.preview = concatUrl("http://mangachan.me/", t.attr("src")); - t = o.select("div.genre").first(); - if (t != null) { - manga.genres = t.text(); - } - manga.provider = MangachanProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - final Document document = getPage(mangaInfo.path); - Element e = document.body(); - summary.description = e.getElementById("description").text().trim(); - summary.preview = concatUrl("http://mangachan.me/", e.getElementById("cover").attr("src")); - MangaChapter chapter; - Elements els = e.select("table.table_cha"); - els = els.select("a"); - for (Element o : els) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = concatUrl("http://mangachan.me/", o.attr("href")); - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - int start = 0; - String s; - Elements es = document.body().select("script"); - for (Element o : es) { - s = o.html(); - start = s.indexOf("fullimg\":["); - if (start != -1) { - start += 9; - int p = s.lastIndexOf("]") + 1; - s = s.substring(start, p); - JSONArray array = new JSONArray(s); - for (int i = 0; i < array.length() - 1; i++) { - page = new MangaPage(array.getString(i)); - page.provider = MangachanProvider.class; - pages.add(page); - } - return pages; - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return mangaPage.path; - } - - @Override - public String getName() { - return "Манга-тян"; - } - - @Override - public String[] getSortTitles(Context context) { - return AppHelper.getStringArray(context, sorts); - } - - @Nullable - @Override - public String[] getGenresTitles(Context context) { - return AppHelper.getStringArray(context, genres); - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return null; - } - MangaList list = new MangaList(); - Document document = getPage("http://mangachan.me/?do=search&subaction=search&story=" + query); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.content_row"); - for (Element o : elements) { - manga = new MangaInfo(); - - t = o.select("h2").first(); - t = t.child(0); - manga.name = t.text(); - manga.path = t.attr("href"); - t = o.select("img").first(); - manga.preview = concatUrl("http://mangachan.me/", t.attr("src")); - t = o.select("div.genre").first(); - if (t != null) { - manga.genres = t.text(); - } - manga.provider = MangachanProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - protected String getAuthCookie() { - if (sAuthCookie == null) { - sAuthCookie = ""; - String login = getStringPreference("login", ""); - String password = getStringPreference("password", ""); - if (!TextUtils.isEmpty(login) && !TextUtils.isEmpty(password)) { - auth(login, password, null); - } - } - return sAuthCookie; - } - - @SuppressWarnings({"WeakerAccess", "unused"}) - public static boolean auth(String login, String password, String arg3) { - CookieParser cp = NetworkUtils.authorize( - "http://mangachan.me/", - "login", - "submit", - "login_name", - login, - "login_password", - password, - "image", - "yay" - ); - if (cp == null || TextUtils.isEmpty(cp.getValue("dle_user_id")) || "deleted".equals(cp.getValue("dle_user_id"))) { - Log.d("AUTH", "fail"); - return false; - } else { - Log.d("AUTH", "OK"); - sAuthCookie = cp.toString(); - return true; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/MintMangaProvider.java b/app/src/main/java/org/nv95/openmanga/providers/MintMangaProvider.java deleted file mode 100755 index c15a2b69..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/MintMangaProvider.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.text.Html; - -import org.json.JSONArray; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.FileLogger; - -import java.util.ArrayList; - -/** - * Created by nv95 on 30.09.15. - */ -public class MintMangaProvider extends MangaProvider { - - protected static final int genres[] = {R.string.genre_all, R.string.genre_art, R.string.genre_bara, R.string.genre_action, R.string.genre_martialarts, R.string.genre_vampires, R.string.genre_harem, - R.string.genre_genderbender, R.string.genre_hero_fantasy, R.string.genre_detective, R.string.genre_josei, - R.string.genre_doujinshi, R.string.genre_drama, R.string.genre_game, R.string.genre_historical, R.string.genre_cyberpunk, - R.string.genre_comedy, R.string.genre_mecha, R.string.genre_mystery, - R.string.genre_sci_fi, R.string.genre_natural, R.string.genre_postapocalipse, R.string.genre_adventure, - R.string.genre_psychological, R.string.genre_romance, R.string.genre_samurai, R.string.genre_supernatural, - R.string.genre_shoujo, R.string.genre_shoujo_ai, R.string.genre_shounen, R.string.genre_shounen_ai, - R.string.genre_sports, R.string.genre_seinen, R.string.genre_tragedy, R.string.genre_thriller, - R.string.genre_horror, R.string.genre_fantastic, R.string.genre_fantasy, - R.string.genre_school, R.string.genre_erotica, R.string.genre_ecchi, R.string.genre_yuri, R.string.genre_yaoi - }; - protected static final String genreUrls[] = {"art", "bara", "action", "martial_arts", "vampires", "harem", - "gender_intriga", "heroic_fantasy", "detective", "josei", "doujinshi", "drama", "game", - "historical", "cyberpunk", "comedy", "mecha", "mystery", - "sci_fi", "natural", "postapocalipse", "adventure", "psychological", "romance", "samurai", - "supernatural", "shoujo", "shoujo_ai", "shounen", "shounen_ai", "sports", "seinen", - "tragedy", "thriller", "horror", "fantastic", "fantasy", - "school", "erotica", "ecchi", "yuri", "yaoi" - }; - - public MintMangaProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://mintmanga.com/list" + - (genre == 0 ? "" : "/genre/" + genreUrls[genre - 1]) - + "?sortType=" + ReadmangaRuProvider.sortUrls[sort] + "&offset=" + page * 70 + "&max=70"); - MangaInfo manga; - Element t; - Element h3, h4; - Elements elements = document.body().select("div.col-sm-6"); - final boolean lc = getBooleanPreference("localized_names", true); - for (Element o : elements) { - manga = new MangaInfo(); - h4 = o.select("h4").first(); - h3 = o.select("h3").first(); - manga.name = lc && h4 != null ? h4.text() : h3.text(); - manga.subtitle = lc ? h3.text() : (h4 == null ? "" : h4.text()); - manga.genres = o.select("a.element-link").text(); - manga.path = "http://mintmanga.com" + o.select("a").first().attr("href"); - manga.preview = o.select("img").first().attr("data-original"); - manga.provider = MintMangaProvider.class; - if (!o.select("span.mangaCompleted").isEmpty()) { - manga.status = MangaInfo.STATUS_COMPLETED; - } - t = o.select("div.rating").first(); - manga.rating = t == null ? 0 : Byte.parseByte(t.attr("title").substring(0, 3).replace(".","")); - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - String descr = e.select("div.manga-description").first().html(); - int p = descr.indexOf(" 0) - descr = descr.substring(0, p); - summary.description = Html.fromHtml(descr).toString().trim(); - summary.preview = e.select("div.picture-fotorama").first().child(0).attr("data-full"); - MangaChapter chapter; - e = e.select("table.table").first(); - if (e == null) { - return summary; - } - for (Element o : e.select("a")) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = "http://mintmanga.com" + o.attr("href") + "?mature=1"; - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - int start; - String s; - Elements es = document.body().select("script"); - for (Element o : es) { - s = o.html(); - start = s.indexOf("rm_h.init("); - if (start != -1) { - start += 10; - int p = s.lastIndexOf("]") + 1; - s = s.substring(start, p); - JSONArray array = new JSONArray(s); - JSONArray o1; - for (int i = 0; i < array.length(); i++) { - o1 = array.getJSONArray(i); - page = new MangaPage(o1.getString(1) + o1.getString(0) + o1.getString(2)); - page.path = concatUrl("http://mintmanga.com/", page.path); - page.provider = MintMangaProvider.class; - pages.add(page); - p++; - } - return pages; - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return mangaPage.path; - } - - @Override - public String getName() { - return "MintManga"; - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean hasSort() {return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - public String[] getSortTitles(Context context) { - return super.getTitles(context, ReadmangaRuProvider.sorts); - } - - @Override - public String[] getGenresTitles(Context context) { - return super.getTitles(context, genres); - } - - //advanced-------- - - - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return MangaList.empty(); - } - MangaList list = new MangaList(); - String data[] = new String[]{ - "q", query.replace(' ','_') - }; - Document document = postPage("http://mintmanga.com/search/advanced", data); - MangaInfo manga; - Element r; - Element h3, h4; - Elements elements = document.body().select("div.col-sm-6"); - final boolean lc = getBooleanPreference("localized_names", true); - for (Element o : elements) { - manga = new MangaInfo(); - h4 = o.select("h4").first(); - h3 = o.select("h3").first(); - manga.name = lc && h4 != null ? h4.text() : h3.text(); - manga.subtitle = lc ? h3.text() : (h4 == null ? "" : h4.text()); - manga.genres = o.select("a.element-link").text(); - manga.path = concatUrl("http://mintmanga.com/", o.select("a").first().attr("href")); - manga.preview = o.select("img").first().attr("data-original"); - r = o.select("div.rating").first(); - manga.rating = r == null ? 0 : Byte.parseByte(r.attr("title").substring(0, 3).replace(".","")); - manga.provider = MintMangaProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/providers/NewChaptersProvider.java b/app/src/main/java/org/nv95/openmanga/providers/NewChaptersProvider.java deleted file mode 100644 index be462cbc..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/NewChaptersProvider.java +++ /dev/null @@ -1,273 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.NonNull; -import android.support.annotation.WorkerThread; -import android.util.Log; - -import org.nv95.openmanga.helpers.StorageHelper; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaUpdateInfo; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.utils.FileLogger; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Map; -import java.util.TreeMap; - -/** - * Created by nv95 on 18.03.16. - */ -public class NewChaptersProvider { - - private static final String TABLE_NAME = "new_chapters"; - private static final int COLUMN_ID = 0; - private static final int COLUMN_CHAPTERS_LAST = 1; - private static final int COLUMN_CHAPTERS = 2; - - private final Context mContext; - private final StorageHelper mStorageHelper; - private static WeakReference instanceReference = new WeakReference<>(null); - - public static NewChaptersProvider getInstance(Context context) { - NewChaptersProvider instance = instanceReference.get(); - if (instance == null) { - instance = new NewChaptersProvider(context); - instanceReference = new WeakReference<>(instance); - } - return instance; - } - - private NewChaptersProvider(Context context) { - this.mContext = context; - mStorageHelper = new StorageHelper(mContext); - } - - public void markAsViewed(int mangaId) { - int chaptersCount = -1; - Cursor cursor = null; - try { - SQLiteDatabase database = mStorageHelper.getReadableDatabase(); - cursor = database.query(TABLE_NAME, null, "id=?", new String[]{String.valueOf(mangaId)}, null, null, null); - if (cursor.moveToFirst()) { - chaptersCount = cursor.getInt(COLUMN_CHAPTERS); - } - cursor.close(); - cursor = null; - if (chaptersCount != -1) { - database = mStorageHelper.getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put("id", mangaId); - cv.put("chapters_last", chaptersCount); - cv.put("chapters", chaptersCount); - if (database.update(TABLE_NAME, cv, "id=?", new String[]{String.valueOf(mangaId)}) == 0) { - database.insert(TABLE_NAME, null, cv); - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - public void markAllAsViewed() { - Map map = new TreeMap<>(); - Cursor cursor = null; - SQLiteDatabase database = null; - try { - database = mStorageHelper.getReadableDatabase(); - cursor = database.query(TABLE_NAME, null, null, null, null, null, null); - if (cursor.moveToFirst()) { - do { - map.put(cursor.getInt(COLUMN_ID), cursor.getInt(COLUMN_CHAPTERS)); - } while (cursor.moveToNext()); - } - cursor.close(); - cursor = null; - database = mStorageHelper.getWritableDatabase(); - database.beginTransaction(); - for (Integer o : map.keySet()) { - ContentValues cv = new ContentValues(); - cv.put("id", o); - cv.put("chapters_last", map.get(o)); - cv.put("chapters", map.get(o)); - if (database.update(TABLE_NAME, cv, "id=?", new String[]{String.valueOf(o)}) == 0) { - database.insert(TABLE_NAME, null, cv); - } - } - database.setTransactionSuccessful(); - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - if (database != null) { - database.endTransaction(); - } - } - } - - @NonNull - public Map getLastUpdates() { - Map map = new TreeMap<>(); - Cursor cursor = null; - try { - cursor = mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, null, null, null, null, null, null); - if (cursor.moveToFirst()) { - int chpt; - do { - chpt = cursor.getInt(COLUMN_CHAPTERS_LAST); - if (chpt != 0) { - chpt = cursor.getInt(COLUMN_CHAPTERS) - chpt; - if (chpt > 0) { - map.put(cursor.getInt(COLUMN_ID), chpt); - } - } - } while (cursor.moveToNext()); - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return map; - } - - /** - * @param mangaId id of manga - * @param chapters count of chapters in manga now - */ - public void storeChaptersCount(int mangaId, int chapters) { - SQLiteDatabase database = null; - try { - database = mStorageHelper.getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put("id", mangaId); - cv.put("chapters", chapters); - if (database.update(TABLE_NAME, cv, "id=?", new String[]{String.valueOf(mangaId)}) == 0) { - cv.put("chapters_last", chapters); - database.insert(TABLE_NAME, null, cv); - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - } - - /** - * @return map of numbers of last user viewed chapters - */ - @NonNull - private Map getChaptersMap() { - Map map = new TreeMap<>(); - Cursor cursor = null; - try { - cursor = mStorageHelper.getReadableDatabase() - .query(TABLE_NAME, null, null, null, null, null, null); - if (cursor.moveToFirst()) { - do { - map.put(cursor.getInt(COLUMN_ID), cursor.getInt(COLUMN_CHAPTERS_LAST)); - } while (cursor.moveToNext()); - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return map; - } - - /** - * @return #true if has as minimum one update - */ - public boolean hasStoredUpdates() { - boolean res = false; - Cursor cursor = null; - SQLiteDatabase database = null; - try { - database = mStorageHelper.getReadableDatabase(); - cursor = database.query(TABLE_NAME, null, null, null, null, null, null); - if (cursor.moveToFirst()) { - do { - int chpt; - do { - chpt = cursor.getInt(COLUMN_CHAPTERS_LAST); - if (chpt != 0) { - chpt = cursor.getInt(COLUMN_CHAPTERS) - chpt; - if (chpt > 0) { - res = true; - break; - } - } - } while (cursor.moveToNext()); - } while (cursor.moveToNext()); - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return res; - } - - @WorkerThread - public MangaUpdateInfo[] checkForNewChapters() { - FavouritesProvider favs = FavouritesProvider.getInstance(mContext); - try { - MangaList mangas = favs.getList(0, 0, 0); - Map map = getChaptersMap(); - MangaProvider provider; - int key; - ArrayList updates = new ArrayList<>(); - for (MangaInfo o : mangas) { - if (o.provider.equals(LocalMangaProvider.class)) { - continue; - } - try { - provider = MangaProviderManager.instanceProvider(mContext, o.provider); - key = o.hashCode(); - MangaUpdateInfo upd = new MangaUpdateInfo(key); - upd.mangaName = o.name; - upd.lastChapters = map.containsKey(key) ? map.get(key) : -1; - upd.chapters = provider.getDetailedInfo(o).getChapters().size(); - Log.d("UPD", upd.mangaName + ": " + upd.lastChapters + " -> " + upd.chapters); - if (upd.chapters > upd.lastChapters) { - if (upd.lastChapters == -1) { - upd.lastChapters = upd.chapters; - } else { - updates.add(upd); - } - storeChaptersCount(key, upd.chapters); - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - } - return updates.toArray(new MangaUpdateInfo[updates.size()]); - } catch (Exception e) { - FileLogger.getInstance().report(e); - return null; - } - } - - @Override - protected void finalize() throws Throwable { - mStorageHelper.close(); - super.finalize(); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/PuzzmosProvider.java b/app/src/main/java/org/nv95/openmanga/providers/PuzzmosProvider.java deleted file mode 100755 index d0c9eceb..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/PuzzmosProvider.java +++ /dev/null @@ -1,197 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.support.annotation.Nullable; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; - -import java.net.URLEncoder; -import java.util.ArrayList; - -/** - * Created by nv95 on 18.12.15. - */ -public class PuzzmosProvider extends MangaProvider { - - protected static final int sorts[] = {R.string.sort_popular, R.string.sort_updated, R.string.sort_alphabetical}; - protected static final String sortUrls[] = {"views&sorting-type=DESC", "lastUpdate&sorting-type=DESC", "name"}; - protected static final int genres[] = {R.string.genre_all, R.string.genre_action, R.string.genre_military, R.string.genre_sfiction, R.string.genre_magic, - R.string.genre_genderbender, R.string.genre_supernatural, R.string.genre_doujinshi, R.string.genre_martialarts, R.string.genre_drama, R.string.genre_ecchi, - R.string.genre_fantasy, R.string.genre_fantastic, R.string.genre_tension, R.string.genre_mystery, R.string.genre_daily, R.string.genre_harem, R.string.genre_josei, R.string.genre_comedy, R.string.genre_horror, - R.string.genre_adventure, R.string.genre_music, R.string.genre_school, R.string.genre_oneshot, R.string.genre_game, R.string.genre_parodi, R.string.genre_police, R.string.genre_psychological, R.string.genre_robotlar, R.string.genre_shounen, - R.string.genre_shoujo, R.string.genre_romance, R.string.genre_seinen, R.string.genre_smut, R.string.genre_sports, - R.string.genre_seytanlar, R.string.genre_historical, R.string.genre_tragedy, R.string.genre_uzay, R.string.genre_vampires, R.string.web, R.string.genre_yetiskin, R.string.genre_yuri, R.string.genre_yaoi}; - protected static final String genreUrls[] = {"aksiyon", "askeri", "bilim+kurgu", "büyü", - "cinsiyet+değişimi", "doğa+üstü", "doujinshi", "dövüş+sanatları", "dram", "ecchi", - "fantezi", "fantastik", "gerilim", "gizem", "günlük+yaşam", "harem", "josei", "komedi", - "korku", "macera", "müzik", "okul+hayatı", "one+shot", "oyun", "parodi", "polisiye", "psikolojik", - "robotlar", "shounen", "shoujo", "romantizm", "seinen", "smut", "spor", "şeytanlar", "tarihi", "trajedi", "uzay", - "vampir", "webtoon", "yetişkin", "yuri", "yaoi"}; - - public PuzzmosProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://puzzmos.com/directory?sorting=" + - sortUrls[sort] + - (genre == 0 ? "" : "&genre=" + genreUrls[genre - 1]) - + "&Sayfa=" + (page + 1)); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.media"); - for (Element o : elements) { - manga = new MangaInfo(); - t = o.select("h4").first(); - if (t == null) { - continue; - } - manga.name = t.text(); - try { - manga.genres = o.select("small").first().text(); - } catch (Exception e) { - manga.genres = ""; - } - manga.path = o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - manga.provider = PuzzmosProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - summary.description = e.select("p").first().text().trim(); - //genres.preview = e.select("img.thumbnail").first().attr("src"); - MangaChapter chapter; - e = e.select("table.table").last(); - for (Element o : e.select("a")) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = o.attr("href"); - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - String s; - MangaPage page; - try { - Document document = getPage(readLink); - Elements elements = document.body().select("select.input-sm").first().select("option"); - for (Element o : elements) { - s = o.attr("value"); - page = new MangaPage(s); - page.provider = PuzzmosProvider.class; - pages.add(page); - } - } catch (Exception e) { - e.printStackTrace(); - } - return pages; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - try { - Document document = getPage(mangaPage.path); - return document.body().select("img").first().attr("src"); - } catch (Exception e) { - return null; - } - } - - @Override - public String getName() { - return "PuzzManga"; - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://puzzmos.com/directory?q=" + - URLEncoder.encode(query, "UTF-8") - + "&Sayfa=" + page + 1); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.media"); - for (Element o : elements) { - manga = new MangaInfo(); - t = o.select("h4").first(); - if (t == null) { - continue; - } - manga.name = t.text(); - try { - manga.subtitle = o.select("small").first().text(); - } catch (Exception e) { - manga.subtitle = ""; - } - manga.genres = o.select("a.element-link").text(); - manga.path = o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - manga.provider = PuzzmosProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - public String[] getSortTitles(Context context) { - return super.getTitles(context, sorts); - } - - @Override - public String[] getGenresTitles(Context context) { - return super.getTitles(context, genres); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/ReadmangaRuProvider.java b/app/src/main/java/org/nv95/openmanga/providers/ReadmangaRuProvider.java deleted file mode 100755 index da8138d9..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/ReadmangaRuProvider.java +++ /dev/null @@ -1,222 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.text.Html; - -import org.json.JSONArray; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.FileLogger; - -import java.util.ArrayList; - -/** - * Created by nv95 on 30.09.15. - * provider for http://readmanga.me/ - */ -public class ReadmangaRuProvider extends MangaProvider { - - protected static final int sorts[] = {R.string.sort_popular, R.string.sort_latest, R.string.sort_updated, R.string.sort_rating}; - protected static final String sortUrls[] = {"popular","created", "updated", "votes"}; - protected static final int genres[] = {R.string.genre_all, R.string.genre_art, R.string.genre_action, R.string.genre_martialarts, R.string.genre_vampires, R.string.genre_harem, - R.string.genre_genderbender, R.string.genre_hero_fantasy, R.string.genre_detective, R.string.genre_josei, - R.string.genre_doujinshi, R.string.genre_drama, R.string.genre_game, R.string.genre_historical, - R.string.genre_codomo, R.string.genre_comedy, R.string.maho_shoujo, R.string.genre_mecha, R.string.genre_mystery, - R.string.genre_sci_fi, R.string.genre_natural, R.string.genre_postapocalipse, R.string.genre_adventure, - R.string.genre_psychological, R.string.genre_romance, R.string.genre_samurai, R.string.genre_supernatural, - R.string.genre_shoujo, R.string.genre_shoujo_ai, R.string.genre_shounen, R.string.genre_shounen_ai, - R.string.genre_sports, R.string.genre_seinen, R.string.genre_tragedy, R.string.genre_thriller, - R.string.genre_horror, R.string.genre_fantastic, R.string.genre_fantasy, - R.string.genre_school, R.string.genre_ecchi, R.string.genre_yuri - }; - static final String genreUrls[] = {"art", "action", "martial_arts", "vampires", "harem", - "gender_intriga", "heroic_fantasy", "detective", "josei", "doujinshi", "drama", "game", - "historical", "codomo", "comedy", "maho_shoujo", "mecha", "mystery", - "sci_fi", "natural", "postapocalipse", "adventure", "psychological", "romance", "samurai", - "supernatural", "shoujo", "shoujo_ai", "shounen", "shounen_ai", "sports", "seinen", - "tragedy", "thriller", "horror", "fantastic", "fantasy", - "school", "ecchi", "yuri" - }; - - @SuppressWarnings("WeakerAccess") - public ReadmangaRuProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://readmanga.me/list" + - (genre == 0 ? "" : "/genre/" + genreUrls[genre - 1]) - + "?sortType=" + sortUrls[sort] + "&offset=" + page * 70 + "&max=70"); - MangaInfo manga; - Element t, h3, h4; - Elements elements = document.body().select("div.col-sm-6"); - final boolean lc = getBooleanPreference("localized_names", true); - for (Element o : elements) { - manga = new MangaInfo(); - h4 = o.select("h4").first(); //Nullable - h3 = o.select("h3").first(); //NonNull - manga.name = lc && h4 != null ? h4.text() : h3.text(); - manga.subtitle = lc ? h3.text() : (h4 == null ? "" : h4.text()); - manga.genres = o.select("a.element-link").text(); - manga.path = concatUrl("http://readmanga.me/", o.select("a").first().attr("href")); - try { - manga.preview = o.select("img").first().attr("data-original"); - } catch (Exception e) { - manga.preview = ""; - } - manga.provider = ReadmangaRuProvider.class; - if (!o.select("span.mangaCompleted").isEmpty()) { - manga.status = MangaInfo.STATUS_COMPLETED; - } - manga.id = manga.path.hashCode(); - t = o.select("div.rating").first(); - manga.rating = t == null ? 0 : Byte.parseByte(t.attr("title").substring(0, 3).replace(".","")); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - String descr = e.select("div.manga-description").first().html(); - int p = descr.indexOf(" 0) - descr = descr.substring(0, p); - summary.description = Html.fromHtml(descr).toString().trim(); - summary.preview = e.select("div.picture-fotorama").first().child(0).attr("data-full"); - MangaChapter chapter; - e = e.select("table.table").first(); - if (e == null) { - return summary; - } - for (Element o : e.select("a")) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = "http://readmanga.me" + o.attr("href") + "?mature=1"; - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - int start; - String s; - Elements es = document.body().select("script"); - for (Element o : es) { - s = o.html(); - start = s.indexOf("rm_h.init("); - if (start != -1) { - start += 10; - int p = s.lastIndexOf("]") + 1; - s = s.substring(start, p); - JSONArray array = new JSONArray(s); - JSONArray o1; - for (int i = 0; i < array.length(); i++) { - o1 = array.getJSONArray(i); - page = new MangaPage(o1.getString(1) + o1.getString(0) + o1.getString(2)); - if (page.path.startsWith("/")) { - page.path = "http://readmanga.me" + page.path; - } - page.provider = ReadmangaRuProvider.class; - pages.add(page); - } - return pages; - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return mangaPage.path; - } - - @Override - public String getName() { - return "ReadManga"; - } - - @Override - public boolean hasGenres() { - return true; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - public String[] getSortTitles(Context context) { - return super.getTitles(context, sorts); - } - - @Override - public String[] getGenresTitles(Context context) { - return super.getTitles(context, genres); - } - - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return MangaList.empty(); - } - MangaList list = new MangaList(); - String data[] = new String[]{ - "q", query.replace(' ','_') - }; - Document document = postPage("http://readmanga.me/search/advanced", data); - MangaInfo manga; - Element r; - Element h4, h3; - final boolean lc = getBooleanPreference("localized_names", true); - Elements elements = document.body().getElementById("mangaResults").select("div.col-sm-6"); - for (Element o : elements) { - manga = new MangaInfo(); - h4 = o.select("h4").first(); - h3 = o.select("h3").first(); - manga.name = lc && h4 != null ? h4.text() : h3.text(); - manga.subtitle = lc ? h3.text() : (h4 == null ? "" : h4.text()); - manga.genres = o.select("a.element-link").text(); - manga.path = concatUrl("http://readmanga.me/", o.select("a").first().attr("href")); - manga.preview = o.select("img").first().attr("data-original"); - r = o.select("div.rating").first(); - manga.rating = r == null ? 0 : Byte.parseByte(r.attr("title").substring(0, 3).replace(".","")); - manga.provider = ReadmangaRuProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/RecommendationsProvider.java b/app/src/main/java/org/nv95/openmanga/providers/RecommendationsProvider.java deleted file mode 100644 index e5d4b05c..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/RecommendationsProvider.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.NonNull; -import android.text.TextUtils; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.StorageHelper; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.providers.staff.ProviderSummary; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; - -/** - * Created by nv95 on 21.03.16. - */ -public class RecommendationsProvider extends MangaProvider { - - @NonNull - private static WeakReference instanceReference = new WeakReference<>(null); - private final MangaProviderManager mProviderManager; - private final Context mContext; - private final StorageHelper mStorageHelper; - private final boolean[] mConfig = new boolean[3]; - - private RecommendationsProvider(Context context) { - super(context); - mContext = context; - mProviderManager = new MangaProviderManager(mContext); - mStorageHelper = new StorageHelper(mContext); - } - - public static RecommendationsProvider getInstance(Context context) { - RecommendationsProvider instance = instanceReference.get(); - if (instance == null) { - instance = new RecommendationsProvider(context); - instanceReference = new WeakReference<>(instance); - } - instance.updateConfig(); - return instance; - } - - private ArrayList getStatGenres(boolean fav, boolean hist) { - final ArrayList genres = new ArrayList<>(); - SQLiteDatabase database = null; - Cursor cursor = null; - try { - database = mStorageHelper.getReadableDatabase(); - if (fav) { - cursor = database.query("favourites", new String[]{"summary"}, null, null, null, null, null); - if (cursor.moveToFirst()) { - do { - String s = cursor.getString(0); - if (!TextUtils.isEmpty(s)) { - Collections.addAll(genres, s.split("[,]?\\s")); - } - } while (cursor.moveToNext()); - } - cursor.close(); - cursor = null; - } - if (hist) { - cursor = database.query("history", new String[]{"summary"}, null, null, null, null, null); - if (cursor.moveToFirst()) { - do { - String s = cursor.getString(0); - if (!TextUtils.isEmpty(s)) { - Collections.addAll(genres, s.toLowerCase().split("[,]?\\s")); - } - } while (cursor.moveToNext()); - } - cursor.close(); - cursor = null; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return genres; - } - - private int checkGenres(String summary, ArrayList genres) { - if (summary == null || summary.length() == 0) { - return 0; - } - if (genres.isEmpty()) { - return 100; - } - String[] parsed = summary.toLowerCase().split("[,]?\\s"); - int coincidences = 0; - for (String o: parsed) { - if (genres.contains(o)) { - coincidences++; - } - } - return coincidences * 100 / parsed.length; - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - final ArrayList genres = getStatGenres(mConfig[0], mConfig[1]); - final List providers = mProviderManager.getEnabledOrderedProviders(); - final MangaList mangas = new MangaList(); - final Random random = new Random(); - final int groupCount = Math.min(providers.size(), 4); - final int groupSize = 20 / groupCount; - MangaList tempList; - MangaInfo manga; - boolean atLeastOne = false; - for (int i=0; i= (mConfig[2] ? 99 : 49)) { - mangas.add(manga); - k++; - } - } - } catch (Exception e) { - //ы - } - } - Collections.shuffle(mangas); - return atLeastOne ? mangas : null; - } - - @Override - protected void finalize() throws Throwable { - mStorageHelper.close(); - super.finalize(); - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - return null; - } - - @Override - public ArrayList getPages(String readLink) { - return null; - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return null; - } - - @Override - public String getName() { - return mContext.getString(R.string.action_recommendations); - } - - public void updateConfig() { - SharedPreferences prefs = mContext.getSharedPreferences("recommendations", Context.MODE_PRIVATE); - mConfig[0] = prefs.getBoolean("fav", true); - mConfig[1] = prefs.getBoolean("hist", true); - mConfig[2] = prefs.getBoolean("match", false); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/ScanFRProvider.java b/app/src/main/java/org/nv95/openmanga/providers/ScanFRProvider.java deleted file mode 100644 index c97f876d..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/ScanFRProvider.java +++ /dev/null @@ -1,192 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.text.Html; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.AppHelper; - -import java.net.URLEncoder; -import java.util.ArrayList; - -/** - * Created by admin on 19.07.17. - */ - -public class ScanFRProvider extends MangaProvider { - - private static final int sorts[] = {R.string.sort_alphabetical, R.string.sort_popular}; - private static final String sortUrls[] = {"name&asc=true", "views&asc=false"}; - - private static final int genres[] = { - R.string.genre_all, R.string.genre_comedy, R.string.genre_drama, - R.string.genre_fantasy, R.string.genre_josei, R.string.genre_mecha, - R.string.genre_oneshot, R.string.genre_romance, R.string.genre_sci_fi, - R.string.genre_shoujo, R.string.genre_shounen, R.string.genre_slice_of_life, - R.string.genre_supernatural, R.string.genre_yaoi, R.string.genre_comics, - R.string.genre_doujinshi, R.string.genre_ecchi, R.string.genre_genderbender, - R.string.genre_mature, R.string.genre_mystery, R.string.genre_psychological, - R.string.genre_school, R.string.genre_seinen, R.string.genre_shoujo_ai, R.string.genre_shounen_ai, - R.string.genre_sports, R.string.genre_tragedy, R.string.genre_yuri, R.string.genre_misc - }; - - private static final int genreUrls[] = {3, 5, 7, 12, 15, 17, 19, 21, 23, 25, 27, 29, - 31, 33, 4, 6, 8, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34}; - - public ScanFRProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://www.scan-fr.com/filterList?page=" + (page + 1) - + "&cat=" + (genre == 0 ? "" : genreUrls[genre - 1]) + "&alpha=&sortBy=" - + sortUrls[sort] + "&author=&tag="); - MangaInfo manga; - for (Element o : document.select(".media")) { - manga = new MangaInfo(); - manga.name = o.select("a.chart-title").first().text(); - manga.subtitle = null; - try { - manga.genres = o.select("div").last().previousElementSibling().text(); - } catch (Exception e) { - manga.genres = ""; - } - manga.path = o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - try { - String scr = o.select("script").first().html(); - int p1 = scr.lastIndexOf('"'); - int p0 = scr.lastIndexOf('"', p1-1); - manga.rating = (byte) (Float.parseFloat(scr.substring(p0+1,p1)) * 20); - } catch (Exception ignored) { - } - //manga.rating = (byte) (Byte.parseByte(o.select("span.rate").first().text().substring(0,3).replace(".","")) * 2); - manga.provider = ScanFRProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - summary.description = Html.fromHtml(e.select("div.well").first().select("p").html()).toString().trim(); - summary.preview = e.select("div.boxed").first().select("img").first().attr("src"); - MangaChapter chapter; - e = e.select("ul.chapters").first(); - for (Element o : e.select("a")) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = o.attr("href"); - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - Element e = document.body().getElementById("all"); - for (Element o : e.select("img")) { - page = new MangaPage(o.attr("data-src")); - page.provider = ScanFRProvider.class; - pages.add(page); - } - return pages; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return mangaPage.path; - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return null; - } - MangaList list = new MangaList(); - JSONObject jo = new JSONObject(getRaw( - "http://www.scan-fr.com/search?query=" + URLEncoder.encode(query, "UTF-8") - )); - MangaInfo manga; - JSONArray ja = jo.getJSONArray("suggestions"); - for (int i = 0; i < ja.length(); i++) { - jo = ja.getJSONObject(i); - manga = new MangaInfo(); - manga.name = jo.getString("value"); - manga.path = "http://www.scan-fr.com/manga/" + jo.getString("data"); - manga.preview = "http://www.scan-fr.com/uploads/manga/" + jo.getString("data") + - "/cover/cover_250x350.jpg"; - manga.provider = ScanFRProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Nullable - @Override - public String[] getSortTitles(Context context) { - return AppHelper.getStringArray(context, sorts); - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public boolean hasGenres() { - return true; - } - - @Nullable - @Override - public String[] getGenresTitles(Context context) { - return AppHelper.getStringArray(context, genres); - } - - @Override - public String getName() { - return "ScanFR"; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/SelfmangaRuProvider.java b/app/src/main/java/org/nv95/openmanga/providers/SelfmangaRuProvider.java deleted file mode 100644 index 395f6c0f..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/SelfmangaRuProvider.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.text.Html; - -import org.json.JSONArray; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.FileLogger; - -import java.util.ArrayList; - -/** - * Created by nv95 on 23.07.16. - */ - -public class SelfmangaRuProvider extends ReadmangaRuProvider { - - public SelfmangaRuProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://selfmanga.ru/list" + - (genre == 0 ? "" : "/genre/" + genreUrls[genre - 1]) - + "?sortType=" + sortUrls[sort] + "&offset=" + page * 70 + "&max=70"); - MangaInfo manga; - Element t, h3, h4; - final boolean lc = getBooleanPreference("localized_names", true); - Elements elements = document.body().select("div.col-sm-6"); - for (Element o : elements) { - manga = new MangaInfo(); - h4 = o.select("h4").first(); - h3 = o.select("h3").first(); - manga.name = lc && h4 != null ? h4.text() : h3.text(); - manga.subtitle = lc ? h3.text() : (h4 == null ? "" : h4.text()); - manga.genres = o.select("a.element-link").text(); - manga.path = "http://selfmanga.ru" + o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("data-original"); - } catch (Exception e) { - manga.preview = ""; - } - manga.provider = SelfmangaRuProvider.class; - if (!o.select("span.mangaCompleted").isEmpty()) { - manga.status = MangaInfo.STATUS_COMPLETED; - } - t = o.select("div.rating").first(); - manga.rating = t == null ? 0 : Byte.parseByte(t.attr("title").substring(0, 3).replace(".","")); - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - String descr = e.select("div.manga-description").first().html(); - int p = descr.indexOf(" 0) - descr = descr.substring(0, p); - summary.description = Html.fromHtml(descr).toString().trim(); - summary.preview = e.select("div.picture-fotorama").first().child(0).attr("data-full"); - MangaChapter chapter; - e = e.select("table.table").first(); - if (e == null) { - return summary; - } - for (Element o : e.select("a")) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = "http://selfmanga.ru" + o.attr("href") + "?mature=1"; - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - int start = 0; - String s; - Elements es = document.body().select("script"); - for (Element o : es) { - s = o.html(); - start = s.indexOf("rm_h.init("); - if (start != -1) { - start += 10; - int p = s.lastIndexOf("]") + 1; - s = s.substring(start, p); - JSONArray array = new JSONArray(s); - JSONArray o1; - for (int i = 0; i < array.length(); i++) { - o1 = array.getJSONArray(i); - page = new MangaPage(o1.getString(1) + o1.getString(0) + o1.getString(2)); - if (page.path.startsWith("/")) { - page.path = "http://selfmanga.ru" + page.path; - } - page.provider = SelfmangaRuProvider.class; - pages.add(page); - } - return pages; - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - return null; - } - - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return MangaList.empty(); - } - MangaList list = new MangaList(); - String data[] = new String[]{ - "q", query.replace(' ','_') - }; - Document document = postPage("http://selfmanga.ru/search/advanced", data); - MangaInfo manga; - Element r, h3, h4; - Elements elements = document.body().select("div.col-sm-6"); - final boolean lc = getBooleanPreference("localized_names", true); - for (Element o : elements) { - manga = new MangaInfo(); - h4 = o.select("h4").first(); - h3 = o.select("h3").first(); - manga.name = lc && h4 != null ? h4.text() : h3.text(); - manga.subtitle = lc ? h3.text() : (h4 == null ? "" : h4.text()); - manga.genres = o.select("a.element-link").text(); - manga.path = "http://selfmanga.ru" + o.select("a").first().attr("href"); - manga.preview = o.select("img").first().attr("data-original"); - r = o.select("div.rating").first(); - manga.rating = r == null ? 0 : Byte.parseByte(r.attr("title").substring(0, 3).replace(".","")); - manga.provider = SelfmangaRuProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public String getName() { - return "SelfManga"; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/TruyenTranhProvider.java b/app/src/main/java/org/nv95/openmanga/providers/TruyenTranhProvider.java deleted file mode 100644 index 064a09ed..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/TruyenTranhProvider.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.support.annotation.Nullable; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; - -import java.net.URLEncoder; -import java.util.ArrayList; - -/** - * Created by unravel22 on 28.03.17. - */ - -public class TruyenTranhProvider extends MangaProvider { - - private static final int sorts[] = {R.string.sort_alphabetical, R.string.sort_popular, R.string.sort_rating}; - private static final String sortUrls[] = {"name-asc", "view-desc", "votepoint-desc"}; - - public TruyenTranhProvider(Context context) { - super(context); - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://truyentranh.net/danh-sach.tall.html?p=" + (page + 1) + "&sort=" + sortUrls[sort]); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.mainpage-manga"); - for (Element o : elements) { - manga = new MangaInfo(); - t = o.select("h4").first(); - if (t == null) { - continue; - } - manga.name = t.text(); - manga.genres = ""; - manga.path = o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - try { - t = o.select(".description").first(); - manga.genres = t.childNode(6).toString(); - t = o.select(".description").get(1); - manga.rating = Byte.parseByte(t.textNodes().get(5).text().trim().replace(".","")); - } catch (Exception e) { - manga.rating = 0; - } - manga.provider = TruyenTranhProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - Document document = getPage(mangaInfo.path); - Element e = document.body(); - summary.preview = e.select(".cover-detail img").attr("src"); - summary.description = e.select("div.manga-content").first().text().trim(); - MangaChapter chapter; - e = e.getElementById("examples"); - for (Element o : e.select("a")) { - chapter = new MangaChapter(); - chapter.name = o.ownText(); - chapter.readLink = o.attr("href"); - chapter.provider = summary.provider; - summary.chapters.add(chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - Element e = document.body().select("div.each-page").first(); - for (Element o : e.select("img")) { - page = new MangaPage(o.attr("src")); - page.provider = TruyenTranhProvider.class; - pages.add(page); - } - return pages; - } catch (Exception e) { - return null; - } - } - - @Override - public String getPageImage(MangaPage mangaPage) { - return mangaPage.path.trim(); - } - - @Override - public String getName() { - return "TruyenTranh"; - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://truyentranh.net/tim-kiem.tall.html?p=" + (page + 1) + "&q=" + URLEncoder.encode(query, "UTF-8")); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.searchlist-items"); - for (Element o : elements) { - manga = new MangaInfo(); - t = o.select("h4").first(); - if (t == null) { - continue; - } - manga.name = t.text(); - manga.genres = ""; - manga.path = o.select("a").first().attr("href"); - try { - manga.preview = o.select("img").first().attr("src"); - } catch (Exception e) { - manga.preview = ""; - } - try { - t = o.select(".description").first(); - manga.genres = t.childNode(6).toString(); - } catch (Exception e) { - manga.rating = 0; - } - manga.provider = TruyenTranhProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public boolean isSearchAvailable() { - return true; - } - - @Override - public boolean hasSort() { - return true; - } - - @Override - public String[] getSortTitles(Context context) { - return super.getTitles(context, sorts); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/YaoiChanProvider.java b/app/src/main/java/org/nv95/openmanga/providers/YaoiChanProvider.java deleted file mode 100644 index b31e4758..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/YaoiChanProvider.java +++ /dev/null @@ -1,195 +0,0 @@ -package org.nv95.openmanga.providers; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.util.Log; - -import org.json.JSONArray; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.utils.CookieParser; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.NetworkUtils; - -import java.util.ArrayList; - -/** - * Created by nv95 on 11.09.16. - */ - -public class YaoiChanProvider extends MangachanProvider { - - private static String sAuthCookie = null; - - public YaoiChanProvider(Context context) { - super(context); - if ("".equals(sAuthCookie)) { - sAuthCookie = null; - } - } - - @Override - public MangaList getList(int page, int sort, int genre) throws Exception { - MangaList list = new MangaList(); - Document document = getPage("http://yaoichan.me/manga/new&n=" + sortUrls[sort] + "?offset=" + page * 20); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.content_row"); - for (Element o : elements) { - manga = new MangaInfo(); - - t = o.select("h2").first(); - t = t.child(0); - manga.name = t.text(); - manga.path = "http://yaoichan.me" + t.attr("href"); - t = o.select("img").first(); - manga.preview = t.attr("src"); - if (manga.preview != null && !manga.preview.startsWith("http://")) { - manga.preview = "http://yaoichan.me" + manga.preview; - } - t = o.select("div.genre").first(); - if (t != null) { - manga.genres = t.text(); - } - manga.provider = YaoiChanProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - public MangaSummary getDetailedInfo(MangaInfo mangaInfo) { - try { - MangaSummary summary = new MangaSummary(mangaInfo); - final Document document = getPage(mangaInfo.path); - Element e = document.body(); - summary.description = e.getElementById("description").text().trim(); - summary.preview = "http://yaoichan.me" + e.getElementById("cover").attr("src"); - MangaChapter chapter; - Elements els = e.select("table.table_cha"); - els = els.select("a"); - for (Element o : els) { - chapter = new MangaChapter(); - chapter.name = o.text(); - chapter.readLink = "http://yaoichan.me" + o.attr("href"); - chapter.provider = summary.provider; - summary.chapters.add(0, chapter); - } - summary.chapters.enumerate(); - return summary; - } catch (Exception e) { - return null; - } - } - - @Override - public ArrayList getPages(String readLink) { - ArrayList pages = new ArrayList<>(); - try { - Document document = getPage(readLink); - MangaPage page; - int start = 0; - String s; - Elements es = document.body().select("script"); - for (Element o : es) { - s = o.html(); - start = s.indexOf("fullimg\":["); - if (start != -1) { - start += 9; - int p = s.lastIndexOf("]") + 1; - s = s.substring(start, p); - JSONArray array = new JSONArray(s); - for (int i = 0; i < array.length() - 1; i++) { - page = new MangaPage(array.getString(i)); - page.provider = YaoiChanProvider.class; - pages.add(page); - } - return pages; - } - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - } - return null; - } - - @Override - public String getName() { - return "Яой-тян"; - } - - @Nullable - @Override - public MangaList search(String query, int page) throws Exception { - if (page > 0) { - return null; - } - MangaList list = new MangaList(); - Document document = getPage("http://yaoichan.me/?do=search&subaction=search&story=" + query, getAuthCookie()); - MangaInfo manga; - Element t; - Elements elements = document.body().select("div.content_row"); - for (Element o : elements) { - manga = new MangaInfo(); - - t = o.select("h2").first(); - t = t.child(0); - manga.name = t.text(); - manga.path = t.attr("href"); - t = o.select("img").first(); - manga.preview = "http://yaoichan.me" + t.attr("src"); - t = o.select("div.genre").first(); - if (t != null) { - manga.genres = t.text(); - } - manga.provider = YaoiChanProvider.class; - manga.id = manga.path.hashCode(); - list.add(manga); - } - return list; - } - - @Override - protected String getAuthCookie() { - if (sAuthCookie == null) { - sAuthCookie = ""; - String login = getStringPreference("login", ""); - String password = getStringPreference("password", ""); - if (!TextUtils.isEmpty(login) && !TextUtils.isEmpty(password)) { - auth(login, password, null); - } - } - return sAuthCookie; - } - - @SuppressWarnings({"WeakerAccess", "unused"}) - public static boolean auth(String login, String password, String arg3) { - CookieParser cp = NetworkUtils.authorize( - "http://yaoichan.me/", - "login", - "submit", - "login_name", - login, - "login_password", - password, - "image", - "yay" - ); - if (cp == null || TextUtils.isEmpty(cp.getValue("dle_user_id")) || "deleted".equals(cp.getValue("dle_user_id"))) { - Log.d("AUTH", "fail"); - return false; - } else { - Log.d("AUTH", "OK"); - sAuthCookie = cp.toString(); - return true; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/staff/Languages.java b/app/src/main/java/org/nv95/openmanga/providers/staff/Languages.java deleted file mode 100644 index 784fa56c..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/staff/Languages.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.nv95.openmanga.providers.staff; - -import java.util.Locale; - -/** - * Created by nv95 on 23.07.16. - */ -public class Languages { - - public static final int EN = 0; - public static final int RU = 1; - public static final int JP = 2; - public static final int TR = 3; - public static final int MULTI = 4; - public static final int VIE = 5; - public static final int FR = 6; - - public static int fromLocale(Locale locale) { - switch (locale.getLanguage()) { - case "ru": - case "uk": - case "be": - case "sk": - case "sl": - case "sr": - return RU; - case "tr": - return TR; - case "fr": - return FR; - default: - return EN; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/staff/MangaProviderManager.java b/app/src/main/java/org/nv95/openmanga/providers/staff/MangaProviderManager.java deleted file mode 100755 index f51d7e15..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/staff/MangaProviderManager.java +++ /dev/null @@ -1,199 +0,0 @@ -package org.nv95.openmanga.providers.staff; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.support.annotation.Nullable; -import android.util.Log; - -import org.nv95.openmanga.providers.EHentaiProvider; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.RecommendationsProvider; -import org.nv95.openmanga.utils.FileLogger; - -import java.lang.reflect.Method; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Created by nv95 on 30.09.15. - */ -public class MangaProviderManager { - - public static final int PROVIDER_LOCAL = -4; - public static final int PROVIDER_RECOMMENDATIONS = -3; - public static final int PROVIDER_FAVOURITES = -2; - public static final int PROVIDER_HISTORY = -1; - - private final Context mContext; - private ArrayList mProviders; - - public MangaProviderManager(Context context) { - mContext = context; - update(); - } - - public void update() { - mProviders = new ArrayList<>(); - String ids = mContext.getSharedPreferences("providers", Context.MODE_PRIVATE).getString("ordered", null); - if (ids == null) { - Collections.addAll(mProviders, Providers.getAll()); - } else { - String[] ss = ids.split("\\|"); - int i; - int count = Providers.getCount(); - for (String o : ss) { - i = Integer.parseInt(o); - if (i < count) { - mProviders.add(Providers.getById(i)); - } - } - for (i=ss.length;i) Class.forName(className)); - } catch (Exception e) { - return null; - } - } - - @Nullable - public static MangaProvider instanceProvider(Context context, Class aClass) { - try { - Method m = aClass.getMethod("getInstance", Context.class); - return (MangaProvider) m.invoke(null, context); - } catch (Exception ignored) { - } - try { - return aClass.getDeclaredConstructor(Context.class).newInstance(context); - } catch (Exception ignored) { - } - return null; - } - - public static boolean needConnectionFor(MangaProvider provider) { - return !(provider instanceof LocalMangaProvider || provider instanceof FavouritesProvider || provider instanceof HistoryProvider); - } - - public static boolean checkConnection(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo ni = cm.getActiveNetworkInfo(); - return ni != null && ni.isAvailable() && ni.isConnected(); - } - - public static boolean isWlan(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo ni = cm.getActiveNetworkInfo(); - return ni != null && ni.getType() == ConnectivityManager.TYPE_WIFI; - } - - public MangaProvider instanceProvider(Class aClass) { - return instanceProvider(mContext, aClass); - } - - public static void saveSortOrder(Context context, MangaProvider provider, int sort) { - context.getSharedPreferences("sort", Context.MODE_PRIVATE) - .edit() - .putInt(provider.getName(), sort) - .apply(); - } - - public static int restoreSortOrder(Context context, MangaProvider provider) { - return context.getSharedPreferences("sort", Context.MODE_PRIVATE).getInt(provider.getName(), 0); - } - - //------------------------------------------- - - public void updateOrder(List providers) { - String ids = ""; - for (ProviderSummary o : providers) { - ids = ids + o.id + "|"; - } - Log.d("SORT", ids); - mContext.getSharedPreferences("providers", Context.MODE_PRIVATE) - .edit() - .putString("ordered", ids.substring(0, ids.length() - 1)) - .apply(); - } - - public List getOrderedProviders() { - return mProviders; - } - - public List getEnabledOrderedProviders() { - return mProviders.subList(0, getProvidersCount()); - } - - - public List getDisabledOrderedProviders() { - return mProviders.subList(getProvidersCount(), mProviders.size()); - } - - public boolean hasDisabledProviders() { - int count = mContext.getSharedPreferences("providers", Context.MODE_PRIVATE).getInt("count", Providers.getCount()); - return count < mProviders.size(); - } - - public int getProvidersCount() { - return Math.min( - mContext.getSharedPreferences("providers", Context.MODE_PRIVATE).getInt("count", Providers.getCount()), - mProviders.size() - ); - } - - public void setProvidersCount(int count) { - mContext.getSharedPreferences("providers", Context.MODE_PRIVATE) - .edit() - .putInt("count", count) - .apply(); - } - - public static void prepareConnection(HttpURLConnection connection) { - URL url = connection.getURL(); - switch (url.getHost().toLowerCase()) { - case "exhentai.org": - if (EHentaiProvider.isAuthorized()) { - connection.addRequestProperty("Cookie", EHentaiProvider.getCookie()); - } - break; - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/staff/ProviderSummary.java b/app/src/main/java/org/nv95/openmanga/providers/staff/ProviderSummary.java deleted file mode 100644 index 0dc6c7b7..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/staff/ProviderSummary.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.nv95.openmanga.providers.staff; - -import android.support.annotation.NonNull; -import android.support.annotation.XmlRes; - -import org.nv95.openmanga.providers.MangaProvider; - -/** - * Created by nv95 on 23.07.16. - */ -public class ProviderSummary { - - public int id; - public String name; - @NonNull - public Class aClass; - public int lang; - @XmlRes - public final int preferences; - - public ProviderSummary(int id, String name, @NonNull Class aClass, - int lang, @XmlRes int preferences) { - this.id = id; - this.name = name; - this.aClass = aClass; - this.lang = lang; - this.preferences = preferences; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ProviderSummary that = (ProviderSummary) o; - - return aClass.equals(that.aClass); - - } - - @Override - public int hashCode() { - return aClass.hashCode(); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/providers/staff/Providers.java b/app/src/main/java/org/nv95/openmanga/providers/staff/Providers.java deleted file mode 100644 index 8c02389e..00000000 --- a/app/src/main/java/org/nv95/openmanga/providers/staff/Providers.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.nv95.openmanga.providers.staff; - -import android.support.annotation.Nullable; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.providers.DesuMeProvider; -import org.nv95.openmanga.providers.EHentaiProvider; -import org.nv95.openmanga.providers.HentaichanProvider; -import org.nv95.openmanga.providers.MangaFoxProvider; -import org.nv95.openmanga.providers.MangaReaderProvider; -import org.nv95.openmanga.providers.MangaTownProvider; -import org.nv95.openmanga.providers.MangachanProvider; -import org.nv95.openmanga.providers.MintMangaProvider; -import org.nv95.openmanga.providers.PuzzmosProvider; -import org.nv95.openmanga.providers.ReadmangaRuProvider; -import org.nv95.openmanga.providers.ScanFRProvider; -import org.nv95.openmanga.providers.SelfmangaRuProvider; -import org.nv95.openmanga.providers.TruyenTranhProvider; -import org.nv95.openmanga.providers.YaoiChanProvider; - -/** - * Created by nv95 on 27.07.16. - */ - -public class Providers { - - private static final ProviderSummary[] mAllProviders = { - new ProviderSummary(0, "ReadManga", ReadmangaRuProvider.class, Languages.RU, R.xml.pref_readmanga), - new ProviderSummary(1, "MintManga", MintMangaProvider.class, Languages.RU, R.xml.pref_readmanga), - new ProviderSummary(2, "Манга-тян", MangachanProvider.class, Languages.RU, R.xml.pref_anychan), - new ProviderSummary(3, "Desu.me", DesuMeProvider.class, Languages.RU, 0), - new ProviderSummary(4, "SelfManga", SelfmangaRuProvider.class, Languages.RU, R.xml.pref_readmanga), - new ProviderSummary(5, "MangaFox", MangaFoxProvider.class, Languages.EN, 0), - new ProviderSummary(6, "MangaTown", MangaTownProvider.class, Languages.EN, 0), - new ProviderSummary(7, "MangaReader", MangaReaderProvider.class, Languages.EN, 0), - new ProviderSummary(8, "E-Hentai", EHentaiProvider.class, Languages.MULTI, R.xml.pref_ehentai), - new ProviderSummary(9, "PuzzManga", PuzzmosProvider.class, Languages.TR, 0), - new ProviderSummary(10, "Яой-тян", YaoiChanProvider.class, Languages.RU, R.xml.pref_anychan), - new ProviderSummary(11, "TruyenTranh", TruyenTranhProvider.class, Languages.VIE, 0), - new ProviderSummary(12, "Хентай-тян", HentaichanProvider.class, Languages.RU, R.xml.pref_henchan), - new ProviderSummary(13, "ScanFR", ScanFRProvider.class, Languages.FR, 0) - }; - - public static ProviderSummary[] getAll() { - return mAllProviders; - } - - @Nullable - public static ProviderSummary getById(int id) { - for (ProviderSummary o : mAllProviders) { - if (id == o.id) { - return o; - } - } - return null; - } - - public static int getCount() { - return mAllProviders.length; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/reader/BookmarkOpenTask.java b/app/src/main/java/org/nv95/openmanga/reader/BookmarkOpenTask.java new file mode 100644 index 00000000..ece34e09 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/BookmarkOpenTask.java @@ -0,0 +1,57 @@ +package org.nv95.openmanga.reader; + +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.core.ObjectWrapper; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.providers.MangaProvider; + +/** + * Created by koitharu on 29.01.18. + */ + +final class BookmarkOpenTask extends WeakAsyncTask> { + + BookmarkOpenTask(ReaderActivity readerActivity) { + super(readerActivity); + } + + @NonNull + @Override + protected ObjectWrapper doInBackground(MangaBookmark... bookmarks) { + try { + final ReaderActivity.Result result = new ReaderActivity.Result(); + final MangaBookmark bookmark = bookmarks[0]; + MangaProvider provider = MangaProvider.get(getObject(), bookmark.manga.provider); + result.mangaDetails = provider.getDetails(bookmark.manga); + result.chapter = CollectionsUtils.findItemById(result.mangaDetails.chapters, bookmark.chapterId); + result.pageId = bookmark.pageId; + return new ObjectWrapper<>(result); + } catch (Exception e) { + e.printStackTrace(); + return new ObjectWrapper<>(e); + } + } + + @Override + protected void onPostExecute(@NonNull ReaderActivity readerActivity, ObjectWrapper result) { + super.onPostExecute(readerActivity, result); + if (result.isSuccess()) { + final ReaderActivity.Result data = result.get(); + readerActivity.onMangaReady(data); + } else { + new AlertDialog.Builder(readerActivity) + .setMessage(ErrorUtils.getErrorMessageDetailed(readerActivity, result.getError())) + .setCancelable(true) + .setOnCancelListener(readerActivity) + .setNegativeButton(R.string.close, readerActivity) + .create() + .show(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/reader/BookmarkTask.java b/app/src/main/java/org/nv95/openmanga/reader/BookmarkTask.java new file mode 100644 index 00000000..73a5fd65 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/BookmarkTask.java @@ -0,0 +1,75 @@ +package org.nv95.openmanga.reader; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import android.widget.Toast; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.MetricsUtils; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.storage.db.BookmarksRepository; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; +import org.nv95.openmanga.reader.loader.PagesCache; + +import java.io.File; + +/** + * Created by koitharu on 22.01.18. + */ + +public final class BookmarkTask extends WeakAsyncTask { + + public BookmarkTask(Context context) { + super(context); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected Boolean doInBackground(Request... requests) { + try { + final MangaBookmark bookmark = new MangaBookmark( + requests[0].manga, + requests[0].chapter.id, + requests[0].page.id, + System.currentTimeMillis() + ); + BookmarksRepository repo = BookmarksRepository.get(getObject()); + ThumbnailsStorage thumbs = new ThumbnailsStorage(getObject()); + File file = PagesCache.getInstance(getObject()).getFileForUrl(requests[0].page.url); + MetricsUtils.Size size = MetricsUtils.getPreferredCellSizeMedium(getObject().getResources()); + Bitmap bitmap = ImageUtils.getThumbnail(file.getPath(), size.width, size.height); //TODO size + thumbs.put(bookmark, bitmap); + if (bitmap != null) { + bitmap.recycle(); + } + return repo.add(bookmark) || repo.update(bookmark); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + protected void onPostExecute(@NonNull Context context, @NonNull Boolean aBoolean) { + Toast.makeText(context, aBoolean ? R.string.bookmark_added : R.string.failed_to_create_bookmark, Toast.LENGTH_SHORT).show(); + } + + static class Request { + + final MangaHeader manga; + final MangaChapter chapter; + final MangaPage page; + + public Request(MangaHeader manga, MangaChapter chapter, MangaPage page) { + this.manga = manga; + this.chapter = chapter; + this.page = page; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ChapterLoader.java b/app/src/main/java/org/nv95/openmanga/reader/ChapterLoader.java new file mode 100644 index 00000000..d9663d92 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ChapterLoader.java @@ -0,0 +1,38 @@ +package org.nv95.openmanga.reader; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.providers.MangaProvider; + +/** + * Created by koitharu on 09.01.18. + */ + +public final class ChapterLoader extends AsyncTaskLoader> { + + private final MangaChapter mChapter; + private final MangaHeader mManga; + + public ChapterLoader(Context context, MangaHeader manga, MangaChapter chapter) { + super(context); + mChapter = chapter; + mManga = manga; + } + + + @Override + public ListWrapper loadInBackground() { + try { + MangaProvider provider = MangaProvider.get(getContext(), mChapter.provider); + return new ListWrapper<>(provider.getPages(mChapter.url)); + } catch (Exception e) { + e.printStackTrace(); + return new ListWrapper<>(e); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ChaptersDialogFragment.java b/app/src/main/java/org/nv95/openmanga/reader/ChaptersDialogFragment.java new file mode 100644 index 00000000..b745e7d0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ChaptersDialogFragment.java @@ -0,0 +1,92 @@ +package org.nv95.openmanga.reader; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import org.nv95.openmanga.AppBaseDialogFragment; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.preview.chapters.ChaptersListAdapter; + +import java.util.ArrayList; + +/** + * Created by koitharu on 09.01.18. + */ + +public final class ChaptersDialogFragment extends AppBaseDialogFragment implements View.OnClickListener, Runnable { + + private RecyclerView mRecyclerView; + private Button mButtonNext; + + private ArrayList mChaptersList; + private long mCurrentId; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Bundle args = getArguments(); + assert args != null; + mChaptersList = args.getParcelableArrayList("chapters"); + mCurrentId = args.getLong("current_id"); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.dialog_chapters, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = view.findViewById(R.id.recyclerView); + mButtonNext = view.findViewById(R.id.button_next); + mButtonNext.setOnClickListener(this); + view.findViewById(R.id.button_close).setOnClickListener(this); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Activity activity = getActivity(); + assert activity != null; + mRecyclerView.addItemDecoration(new DividerItemDecoration(activity, LinearLayoutManager.VERTICAL)); + ChaptersListAdapter adapter = new ChaptersListAdapter(activity, mChaptersList, (ChaptersListAdapter.OnChapterClickListener) activity); + adapter.setCurrentChapterId(mCurrentId); + mRecyclerView.setAdapter(adapter); + mRecyclerView.post(this); + } + + @Override + public void onClick(View v) { + final Activity activity = getActivity(); + if (activity != null && activity instanceof View.OnClickListener) { + ((View.OnClickListener) activity).onClick(v); + } + dismiss(); + } + + @Override + public void run() { + int current = CollectionsUtils.findChapterPositionById(mChaptersList, mCurrentId); + if (current != -1) { + LayoutUtils.scrollToCenter(mRecyclerView, current); + //((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(current, 20); + if (current == mChaptersList.size() - 1) { + mButtonNext.setEnabled(false); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/FitWindowsFrameLayout.java b/app/src/main/java/org/nv95/openmanga/reader/FitWindowsFrameLayout.java new file mode 100644 index 00000000..cba0cd32 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/FitWindowsFrameLayout.java @@ -0,0 +1,67 @@ +package org.nv95.openmanga.reader; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +/** + * Created by koitharu on 08.01.18. + */ + +public final class FitWindowsFrameLayout extends FrameLayout { + + private boolean mSwipeIntercepted = false; + + @Nullable + private Callback mCallback = null; + + public FitWindowsFrameLayout(@NonNull Context context) { + this(context, null, 0); + } + + public FitWindowsFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public FitWindowsFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setFitsSystemWindows(true); + } + + public void setCallback(@Nullable Callback callback) { + mCallback = callback; + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if (ev.getY() < getPaddingTop() || getHeight() - ev.getY() < getPaddingBottom()) { + mSwipeIntercepted = true; + return true; + } + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_HOVER_MOVE: + if (mSwipeIntercepted) + return true; + case MotionEvent.ACTION_UP: + if (mCallback != null && mSwipeIntercepted) { + mCallback.showUi(); + } + case MotionEvent.ACTION_CANCEL: + mSwipeIntercepted = false; + break; + } + return mSwipeIntercepted || super.onTouchEvent(ev); + } + + interface Callback { + + void showUi(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ImageSaveTask.java b/app/src/main/java/org/nv95/openmanga/reader/ImageSaveTask.java new file mode 100644 index 00000000..f62d9f8b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ImageSaveTask.java @@ -0,0 +1,202 @@ +package org.nv95.openmanga.reader; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.view.Gravity; +import android.widget.Toast; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.FilesystemUtils; +import org.nv95.openmanga.common.utils.IntentUtils; +import org.nv95.openmanga.common.utils.network.HttpException; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.ObjectWrapper; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.reader.loader.PagesCache; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.NumberFormat; + +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by koitharu on 30.01.18. + */ + +final class ImageSaveTask extends WeakAsyncTask> implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { + + private final ProgressDialog mProgressDialog; + private final boolean mShare; + + ImageSaveTask(Context context, boolean share) { + super(context); + mShare = share; + mProgressDialog = new ProgressDialog(context); + mProgressDialog.setCancelable(true); + mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), this); + if (context instanceof Activity) { + mProgressDialog.setOwnerActivity((Activity) context); + } + mProgressDialog.setMessage(context.getString(R.string.downloading_image)); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + mProgressDialog.setIndeterminate(true); + mProgressDialog.setOnCancelListener(this); + mProgressDialog.setProgressPercentFormat(NumberFormat.getPercentInstance()); + } + + @Override + protected void onPreExecute(@NonNull Context context) { + mProgressDialog.show(); + } + + @Override + @NonNull + protected ObjectWrapper doInBackground(MangaPage... mangaPages) { + final MangaPage page = mangaPages[0]; + final File destination = page.url != null && page.url.startsWith("file://") ? + new File(page.url.substring(7)) : PagesCache.getInstance(getObject()).getFileForUrl(page.url); + //check if not downloaded + if (!destination.exists()) { + InputStream input = null; + FileOutputStream output = null; + try { + final MangaProvider provider = MangaProvider.get(getObject(), page.provider); + final String pageUrl = provider.getImageUrl(page); + final String domain = MangaProvider.getDomain(page.provider); + final Request request = new Request.Builder() + .url(pageUrl) + .header(NetworkUtils.HEADER_USER_AGENT, NetworkUtils.USER_AGENT_DEFAULT) + .header(NetworkUtils.HEADER_REFERER, "http://" + domain) + .get() + .build(); + final Response response = NetworkUtils.getHttpClient().newCall(request).execute(); + if (!response.isSuccessful()) { + return new ObjectWrapper<>(new HttpException(response.code())); + } + //noinspection ConstantConditions + input = response.body().byteStream(); + output = new FileOutputStream(destination); + final int contentLength = NetworkUtils.getContentLength(response); + final byte[] buffer = new byte[512]; + int total = 0; + int length; + while ((length = input.read(buffer)) >= 0) { + output.write(buffer, 0, length); + total += length; + if (contentLength > 0) { + publishProgress(total, contentLength); + } + if (isCancelled()) { + output.close(); + output = null; + //noinspection ResultOfMethodCallIgnored + destination.delete(); + return ObjectWrapper.badObject(); + } + } + output.flush(); + } catch (Exception e) { + //noinspection ResultOfMethodCallIgnored + destination.delete(); + return new ObjectWrapper<>(e); + } finally { + if (input != null) try { + input.close(); + } catch (IOException ignored) { + } + if (output != null) try { + output.close(); + } catch (IOException ignored) { + } + } + } + if (!destination.exists()) { + return new ObjectWrapper<>(new FileNotFoundException()); + } + final File resultFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), page.id + ".png"); + FileOutputStream out = null; + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeFile(destination.getPath()); + out = new FileOutputStream(resultFile); + bitmap.compress(Bitmap.CompressFormat.PNG, 95, out); + return new ObjectWrapper<>(resultFile); + } catch (Exception e) { + e.printStackTrace(); + return new ObjectWrapper<>(e); + } finally { + if (bitmap != null) { + bitmap.recycle(); + } + if (out != null) { + try { + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + + } + + @Override + protected void onProgressUpdate(@NonNull Context context, Integer[] values) { + mProgressDialog.setIndeterminate(false); + mProgressDialog.setMax(values[1]); + mProgressDialog.setProgress(values[0]); + } + + @Override + protected void onPostExecute(@NonNull Context context, ObjectWrapper result) { + mProgressDialog.dismiss(); + if (result.isFailed()) { + Toast toast = Toast.makeText( + context, + ErrorUtils.getErrorMessage(context, result.getError()), + Toast.LENGTH_SHORT + ); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + return; + } + final File file = result.get(); + FilesystemUtils.scanFile(context, file, null); + if (mShare) { + IntentUtils.shareImage(context, file); + } else { + Toast toast = Toast.makeText( + context, + context.getString(R.string.image_saved), + Toast.LENGTH_SHORT + ); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + + @Override + public void onCancel(DialogInterface dialog) { + if (canCancel()) { + cancel(true); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/reader/OnOverScrollListener.java b/app/src/main/java/org/nv95/openmanga/reader/OnOverScrollListener.java new file mode 100644 index 00000000..9a01f11d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/OnOverScrollListener.java @@ -0,0 +1,14 @@ +package org.nv95.openmanga.reader; + +/** + * Created by koitharu on 30.01.18. + */ + +public interface OnOverScrollListener { + + void onOverScrolledStart(); + + void onOverScrolledEnd(); + + //void onOverScrollCancelled(); +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ReaderActivity.java b/app/src/main/java/org/nv95/openmanga/reader/ReaderActivity.java new file mode 100644 index 00000000..20f006ab --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ReaderActivity.java @@ -0,0 +1,678 @@ +package org.nv95.openmanga.reader; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.LoaderManager; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Loader; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.AppCompatSeekBar; +import android.support.v7.widget.Toolbar; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.FrameLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.AppShortcutHelper; +import org.nv95.openmanga.common.dialogs.BottomSheetMenuDialog; +import org.nv95.openmanga.common.dialogs.MenuDialog; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.IntentUtils; +import org.nv95.openmanga.common.utils.ThemeUtils; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaBookmark; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.storage.db.BookmarksRepository; +import org.nv95.openmanga.core.storage.db.HistoryRepository; +import org.nv95.openmanga.core.storage.files.ThumbnailsStorage; +import org.nv95.openmanga.core.storage.settings.AppSettings; +import org.nv95.openmanga.core.storage.settings.ReaderSettings; +import org.nv95.openmanga.preview.chapters.ChaptersListAdapter; +import org.nv95.openmanga.reader.pager.PagerReaderFragment; +import org.nv95.openmanga.reader.pager.RtlPagerReaderFragment; +import org.nv95.openmanga.reader.thumbview.OnThumbnailClickListener; +import org.nv95.openmanga.reader.thumbview.ThumbViewFragment; +import org.nv95.openmanga.reader.webtoon.WebtoonReaderFragment; +import org.nv95.openmanga.tools.settings.SettingsActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 08.01.18. + */ + +public final class ReaderActivity extends AppBaseActivity implements View.OnClickListener, + SeekBar.OnSeekBarChangeListener, ChaptersListAdapter.OnChapterClickListener, + LoaderManager.LoaderCallbacks>, View.OnSystemUiVisibilityChangeListener, + ReaderCallback, OnThumbnailClickListener, MenuDialog.OnMenuItemClickListener, + DialogInterface.OnClickListener, DialogInterface.OnCancelListener, ReaderModeDialog.OnReaderModeChangeListener, + OnOverScrollListener, FitWindowsFrameLayout.Callback { + + public static final String ACTION_READING_CONTINUE = "org.nv95.openmanga.ACTION_READING_CONTINUE"; + public static final String ACTION_BOOKMARK_OPEN = "org.nv95.openmanga.ACTION_BOOKMARK_OPEN"; + + private static final long PAGE_ID_FIRST = Long.MIN_VALUE; + private static final long PAGE_ID_LAST = Long.MAX_VALUE; + private static final long PAGE_ID_UNDEFINED = 0L; + + private static final int REQUEST_PAGE_SAVE = 61; + private static final int REQUEST_PAGE_SHARE = 62; + + private AppCompatSeekBar mSeekBar; + private ViewGroup mContentPanel; + private Toolbar mToolbar; + private View mBottomBar; + private TextView mTextViewPage; + private ReaderStatusBar mStatusBar; + + private FrameLayout mFrame; + private ReaderFragment mReader = null; + private ReaderSettings mSettings; + private HistoryRepository mHistoryRepository; + private BookmarksRepository mBookmarksRepository; + private MangaDetails mManga; + private MangaChapter mChapter; + private ArrayList mPages; + private long mPageId; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_reader); + mToolbar = findViewById(R.id.toolbar); + setSupportActionBar(mToolbar); + enableHomeAsUp(); + mSettings = AppSettings.get(this).readerSettings; + + mStatusBar = findViewById(R.id.statusBar); + mSeekBar = findViewById(R.id.seekBar); + mContentPanel = findViewById(R.id.contentPanel); + mBottomBar = findViewById(R.id.bottomBar); + mFrame = findViewById(R.id.frame); + mTextViewPage = findViewById(R.id.textView_page); + this.findViewById(R.id.root).setCallback(this); + + mSeekBar.setOnSeekBarChangeListener(this); + findViewById(R.id.action_menu).setOnClickListener(this); + findViewById(R.id.action_thumbnails).setOnClickListener(this); + mStatusBar.setIsActive(mSettings.isStatusBarEnbaled()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getWindow(); + int color = ContextCompat.getColor(this, R.color.transparent_dark); + window.setStatusBarColor(color); + window.setNavigationBarColor(color); + } + + mHistoryRepository = HistoryRepository.get(this); + mBookmarksRepository = BookmarksRepository.get(this); + + final Intent intent = getIntent(); + final String action = intent.getAction(); + if (ACTION_READING_CONTINUE.equals(action)) { + MangaHeader mangaHeader = MangaHeader.from(intent.getExtras()); + new ResumeReadingTask(this).start(mangaHeader); + } else if (ACTION_BOOKMARK_OPEN.equals(action)) { + MangaBookmark bookmark = MangaBookmark.from(intent.getExtras()); + if (bookmark == null) { + Toast.makeText(this, R.string.bookmark_not_found, Toast.LENGTH_SHORT).show(); + finish(); + } + new BookmarkOpenTask(this).start(bookmark); + } else { + mManga = intent.getParcelableExtra("manga"); + mChapter = intent.getParcelableExtra("chapter"); + mPageId = intent.getLongExtra("page_id", PAGE_ID_UNDEFINED); + onMangaReady(); + } + } + + public void onMangaReady() { + assert mManga != null && mChapter != null; + setTitle(mManga.name); + setSubtitle(mChapter.name); + setupReader(mHistoryRepository.getPreset(mManga, mSettings.getDefaultPreset())); + getLoaderManager().initLoader(0, mChapter.toBundle(), this).forceLoad(); + } + + public void onMangaReady(Result result) { + if (result.chapter == null) { + new AlertDialog.Builder(this) + .setMessage(R.string.requested_chapter_not_found) + .setCancelable(true) + .setOnCancelListener(this) + .setNegativeButton(R.string.close, this) + .create() + .show(); + } + this.mManga = result.mangaDetails; + this.mChapter = result.chapter; + this.mPageId = result.pageId; + onMangaReady(); + } + + @Override + protected void onPause() { + if (mHistoryRepository != null && mPages != null) { + if (!mHistoryRepository.quickUpdate(mManga, mChapter, mReader.getCurrentPage())) { + addToHistory(); + } + } + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + setKeepScreenOn(mSettings.isWakelockEnabled()); + mStatusBar.setIsActive(mSettings.isStatusBarEnbaled()); + if (mToolbar.getVisibility() != View.VISIBLE) { + mStatusBar.show(); + } + if (mSettings.isCustomBackground()) { + mFrame.setBackgroundColor(mSettings.getBackgroundColor()); + } else { + mFrame.setBackgroundColor(ThemeUtils.getThemeAttrColor(this, android.R.attr.windowBackground)); + } + } + + @Override + protected void onStop() { + new AppShortcutHelper(this).update(mHistoryRepository); + super.onStop(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mToolbar.getVisibility() != View.VISIBLE) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_reader, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_chapters) { + final ChaptersDialogFragment dialogFragment = new ChaptersDialogFragment(); + final Bundle args = new Bundle(); + args.putParcelableArrayList("chapters", mManga.chapters); + args.putLong("current_id", mChapter.id); + dialogFragment.setArguments(args); + dialogFragment.show(getSupportFragmentManager(), "chapters_list"); + return true; + } else return super.onOptionsItemSelected(item); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.action_menu: + final MangaPage page = mReader.getCurrentPage(); + final MangaBookmark bookmark = mBookmarksRepository.find(mManga, mChapter, page); + final BottomSheetMenuDialog menu = new BottomSheetMenuDialog<>(this); + menu.setItemClickListener(this); + if (bookmark == null) { + menu.addItem(R.id.action_page_bookmark_add, R.drawable.ic_bookmark_add_black, R.string.create_bookmark); + } else { + menu.addItem(R.id.action_page_bookmark_remove, R.drawable.ic_bookmark_remove_black, R.string.remove_bookmark); + } + menu.addItem(R.id.action_page_save, R.drawable.ic_save_white, R.string.save_image) + .addItem(R.id.action_page_share, R.drawable.ic_share_white, R.string.share_image) + .addItem(R.id.action_open_browser, R.drawable.ic_open_in_black, R.string.open_in_browser) + .addItem(R.id.action_reader_mode, R.drawable.ic_aspect_ratio_black, R.string.reader_mode) + .addItem(R.id.action_reader_settings, R.drawable.ic_settings_black, R.string.action_reading_options) + .create(page) + .show(); + break; + case R.id.action_thumbnails: + final ThumbViewFragment dialogFragment = new ThumbViewFragment(); + final Bundle args = new Bundle(); + args.putParcelableArrayList("pages", mPages); + dialogFragment.setArguments(args); + dialogFragment.show(getSupportFragmentManager(), "thumb_view"); + break; + case R.id.button_next: + onOverScrolledEnd(); + break; + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mTextViewPage.setText(getString(R.string.page_x_of_n, progress + 1, seekBar.getMax() + 1)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mReader.scrollToPage(seekBar.getProgress()); + } + + @Override + public void onChapterClick(int pos, MangaChapter chapter) { + android.support.v4.app.Fragment f = getSupportFragmentManager().findFragmentByTag("chapters_list"); + if (f != null && f instanceof DialogFragment) { + ((DialogFragment) f).dismiss(); + } + AnimationUtils.setVisibility(mContentPanel, View.VISIBLE); + mChapter = chapter; + setSubtitle(mChapter.name); + + getLoaderManager().restartLoader(0, mChapter.toBundle(), this).forceLoad(); + } + + @Override + public boolean onChapterLongClick(int pos, MangaChapter chapter) { + return false; + } + + @NonNull + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new ChapterLoader(this, mManga, MangaChapter.from(args)); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper data) { + AnimationUtils.setVisibility(mContentPanel, View.GONE); + if (data.isFailed()) { + new AlertDialog.Builder(this) + .setTitle(R.string.failed_to_load_pages) + .setMessage(ErrorUtils.getErrorMessageDetailed(this, data.getError())) + .setCancelable(true) + .setOnCancelListener(this) + .setPositiveButton(R.string.retry, this) + .setNegativeButton(R.string.close, this) + .create() + .show(); + } else if (data.isEmpty()) { + new AlertDialog.Builder(this) + .setMessage(R.string.no_pages_found) + .setCancelable(true) + .setOnCancelListener(this) + .setNegativeButton(R.string.close, this) + .create() + .show(); + } else { + mPages = data.get(); + mSeekBar.setMax(mPages.size() - 1); + mSeekBar.setProgress(0); + mReader.setPages(mPages); + final int page; + if (mPageId == PAGE_ID_LAST) { + page = mPages.size() - 1; + } else if (mPageId == PAGE_ID_FIRST || mPageId == PAGE_ID_UNDEFINED) { + page = 0; + } else { + page = CollectionsUtils.findPagePositionById(mPages, mPageId); + } + mPageId = PAGE_ID_UNDEFINED; + mReader.scrollToPage(page == -1 ? 0 : page); + addToHistory(); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (isFinishing()) { + return super.onKeyDown(keyCode, event); + } + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + if (mSettings.isVolumeKeysEnabled()) { + if (!mReader.movePrevious()) { + prevChapter(); + } + return true; + } else return super.onKeyDown(keyCode, event); + case KeyEvent.KEYCODE_VOLUME_DOWN: + if (mSettings.isVolumeKeysEnabled()) { + if (!mReader.moveNext()) { + nextChapter(); + } + return true; + } else return super.onKeyDown(keyCode, event); + default: + return super.onKeyDown(keyCode, event); + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (isFinishing()) { + return super.onKeyUp(keyCode, event); + } + switch (keyCode) { + case KeyEvent.KEYCODE_MENU: + toggleUi(); + return true; + case KeyEvent.KEYCODE_SPACE: + if (!mReader.moveNext()) { + nextChapter(); + } + case KeyEvent.KEYCODE_DPAD_UP: + if (!mReader.moveUp()) { + prevChapter(); + } + return true; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (!mReader.moveDown()) { + nextChapter(); + } + return true; + case KeyEvent.KEYCODE_DPAD_LEFT: + if (!mReader.moveLeft()) { + onOverScrolledStart(); + } + return true; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (!mReader.moveRight()) { + onOverScrolledEnd(); + } + return true; + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + return mSettings.isVolumeKeysEnabled() || super.onKeyUp(keyCode, event); + default: + return super.onKeyUp(keyCode, event); + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + @Override + public void onSystemUiVisibilityChange(int visibility) { + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + } + + @Override + public void showUi() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + AnimationUtils.setVisibility(mToolbar, View.VISIBLE); + AnimationUtils.setVisibility(mBottomBar, View.VISIBLE); + } + + private void hideUi() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + AnimationUtils.setVisibility(mToolbar, View.GONE); + AnimationUtils.setVisibility(mBottomBar, View.GONE); + } + + private void addToHistory() { + final MangaPage page = mReader.getCurrentPage(); + if (page != null) { + final MangaHistory history = new MangaHistory(mManga, mChapter, mManga.chapters.size(), page, getReaderPreset()); + mHistoryRepository.updateOrAdd(history); + } + } + + @Override + public void onPageChanged(int page) { + mSeekBar.setProgress(page); + mStatusBar.setStatus(page, mPages.size()); + } + + public void toggleUi() { + if (mToolbar.getVisibility() == View.VISIBLE) { + hideUi(); + mStatusBar.show(); + } else { + showUi(); + mStatusBar.hide(); + } + } + + @Override + public void onThumbnailClick(int position) { + mReader.scrollToPage(position); + } + + @Override + public void onMenuItemClick(int id, MangaPage page) { + switch (id) { + case R.id.action_page_bookmark_add: + new BookmarkTask(this).start(new BookmarkTask.Request( + mManga, + mChapter, + mReader.getCurrentPage() + )); + break; + case R.id.action_page_bookmark_remove: + final MangaBookmark bookmark = mBookmarksRepository.find(mManga, mChapter, page); + if (bookmark != null) { + mBookmarksRepository.remove(bookmark); + new ThumbnailsStorage(this).remove(bookmark); + } + break; + case R.id.action_open_browser: + IntentUtils.openBrowser(this, page.url); + break; + case R.id.action_page_save: + checkPermissions(REQUEST_PAGE_SAVE, Manifest.permission.WRITE_EXTERNAL_STORAGE); + break; + case R.id.action_page_share: + checkPermissions(REQUEST_PAGE_SHARE, Manifest.permission.WRITE_EXTERNAL_STORAGE); + break; + case R.id.action_reader_mode: + if (mHistoryRepository != null && mPages != null) { + final MangaHistory history = new MangaHistory(mManga, mChapter, mManga.chapters.size(), mReader.getCurrentPage(), getReaderPreset()); + mHistoryRepository.updateOrAdd(history); + new ReaderModeDialog(this, history) + .setListener(this) + .show(); + } + break; + case R.id.action_reader_settings: + startActivity(new Intent(this, SettingsActivity.class) + .setAction(SettingsActivity.ACTION_SETTINGS_READER)); + break; + default: + stub(); + } + } + + @Override + protected void onPermissionGranted(int requestCode, String permission) { + final MangaPage page = mReader.getCurrentPage(); + switch (requestCode) { + case REQUEST_PAGE_SAVE: + new ImageSaveTask(this, false).start(page); + break; + case REQUEST_PAGE_SHARE: + new ImageSaveTask(this, true).start(page); + break; + + } + } + + /** + * Only for errors + */ + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: //retry + AnimationUtils.setVisibility(mContentPanel, View.VISIBLE); + getLoaderManager().restartLoader(0, mChapter.toBundle(), this).forceLoad(); + break; + case DialogInterface.BUTTON_NEGATIVE: //close + onCancel(dialog); + } + } + + /** + * Only for errors + */ + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + + @Override + public void onReaderModeChanged(short mode) { + final MangaHistory history = new MangaHistory(mManga, mChapter, mManga.chapters.size(), mReader.getCurrentPage(), mode); + mHistoryRepository.updateOrAdd(history); + setupReader(mode); + } + + @Override + public void onOverScrolledStart() { + if (mReader instanceof RtlPagerReaderFragment) { + nextChapter(); + } else { + prevChapter(); + } + } + + @Override + public void onOverScrolledEnd() { + if (mReader instanceof RtlPagerReaderFragment) { + prevChapter(); + } else { + nextChapter(); + } + } + + private void prevChapter() { + final int pos = mManga.chapters.indexOf(mChapter); + if (pos == -1 || pos == 0) { + return; + } + AnimationUtils.setVisibility(mContentPanel, View.VISIBLE); + mChapter = mManga.chapters.get(pos - 1); + mPageId = PAGE_ID_LAST; + setSubtitle(mChapter.name); + Toast toast = Toast.makeText(this, getString(R.string.prev_chapter, mChapter.name), Toast.LENGTH_SHORT); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + getLoaderManager().restartLoader(0, mChapter.toBundle(), this).forceLoad(); + } + + private void nextChapter() { + final int pos = mManga.chapters.indexOf(mChapter); + if (pos == -1 || pos == mManga.chapters.size() - 1) { + return; + } + AnimationUtils.setVisibility(mContentPanel, View.VISIBLE); + mPageId = PAGE_ID_FIRST; + mChapter = mManga.chapters.get(pos + 1); + setSubtitle(mChapter.name); + Toast toast = Toast.makeText(this, getString(R.string.next_chapter_, mChapter.name), Toast.LENGTH_SHORT); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + getLoaderManager().restartLoader(0, mChapter.toBundle(), this).forceLoad(); + } + + private void setupReader(int preset) { + Bundle savedState; + if (mReader != null) { + savedState = new Bundle(); + mReader.onSaveInstanceState(savedState); + } else { + savedState = null; + } + switch (preset) { + case 0: + mReader = new PagerReaderFragment(); + break; + case 1: + mReader = new RtlPagerReaderFragment(); + break; + case 2: + mReader = new WebtoonReaderFragment(); + break; + default: + stub(); + mReader = new PagerReaderFragment(); + } + mReader.setArguments(savedState); + getFragmentManager().beginTransaction() + .replace(R.id.reader, mReader) + .commit(); + } + + private short getReaderPreset() { + if (mReader == null) { + return mHistoryRepository.getPreset(mManga, mSettings.getDefaultPreset()); + } else if (mReader instanceof WebtoonReaderFragment) { + return 2; + } else if (mReader instanceof RtlPagerReaderFragment) { + return 1; + } else if (mReader instanceof PagerReaderFragment) { + return 0; + } else { + return 0;//TODO + } + } + + final static class Result { + + MangaDetails mangaDetails; + MangaChapter chapter; + long pageId; + } +} + diff --git a/app/src/main/java/org/nv95/openmanga/reader/ReaderCallback.java b/app/src/main/java/org/nv95/openmanga/reader/ReaderCallback.java new file mode 100644 index 00000000..977299fc --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ReaderCallback.java @@ -0,0 +1,10 @@ +package org.nv95.openmanga.reader; + +/** + * Created by koitharu on 09.01.18. + */ + +public interface ReaderCallback { + + void onPageChanged(int page); +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ReaderFragment.java b/app/src/main/java/org/nv95/openmanga/reader/ReaderFragment.java new file mode 100644 index 00000000..6960bd28 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ReaderFragment.java @@ -0,0 +1,132 @@ +package org.nv95.openmanga.reader; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.AppBaseFragment; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by koitharu on 08.01.18. + */ + +public abstract class ReaderFragment extends AppBaseFragment implements ReaderCallback { + + @NonNull + protected final ArrayList mPages = new ArrayList<>(); + private boolean mRestored = false; + + @CallSuper + public void setPages(List pages) { + mPages.clear(); + mPages.addAll(pages); + } + + public ArrayList getPages() { + return mPages; + } + + public abstract int getCurrentPageIndex(); + + @Nullable + public MangaPage getCurrentPage() { + return CollectionsUtils.getOrNull(mPages, getCurrentPageIndex()); + } + + public abstract void scrollToPage(int index); + + public abstract void smoothScrollToPage(int index); + + public boolean scrollToPageById(long id) { + for (int i = 0; i < mPages.size(); i++) { + MangaPage o = mPages.get(i); + if (o.id == id) { + scrollToPage(i); + return true; + } + } + return false; + } + + @Override + public void onPageChanged(int page) { + Activity activity = getActivity(); + if (activity != null && activity instanceof ReaderCallback) { + ((ReaderCallback) activity).onPageChanged(page); + } + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Bundle args = getArguments(); + if (!mRestored && args != null) { + onRestoreState(args); + mRestored = true; + } + } + + public abstract boolean moveLeft(); + + public abstract boolean moveRight(); + + public abstract boolean moveUp(); + + public abstract boolean moveDown(); + + public boolean moveNext() { + final int newPos = getCurrentPageIndex() + 1; + if (newPos < mPages.size()) { + smoothScrollToPage(newPos); + return true; + } else { + return false; + } + } + + public boolean movePrevious() { + final int newPos = getCurrentPageIndex() - 1; + if (newPos >= 0) { + smoothScrollToPage(newPos); + return true; + } else { + return false; + } + } + + @Override + @CallSuper + public void onSaveInstanceState(Bundle outState) { + final MangaPage page = getCurrentPage(); + if (page != null) { + outState.putLong("page_id", page.id); + } + outState.putParcelableArrayList("pages", mPages); + } + + @CallSuper + public void onRestoreState(@NonNull Bundle savedState) { + ArrayList pages = savedState.getParcelableArrayList("pages"); + if (pages != null) { + setPages(pages); + long pageId = savedState.getLong("page_id", 0); + if (pageId != 0) { + scrollToPageById(pageId); + } + } + } + + protected void toggleUi() { + final Activity activity = getActivity(); + if (activity != null && activity instanceof ReaderActivity) { + ((ReaderActivity) activity).toggleUi(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ReaderModeDialog.java b/app/src/main/java/org/nv95/openmanga/reader/ReaderModeDialog.java new file mode 100644 index 00000000..e786ccfe --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ReaderModeDialog.java @@ -0,0 +1,51 @@ +package org.nv95.openmanga.reader; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.MangaHistory; + +/** + * Created by koitharu on 30.01.18. + */ + +final class ReaderModeDialog implements DialogInterface.OnClickListener { + + private final AlertDialog.Builder mBuilder; + @Nullable + private OnReaderModeChangeListener mListener = null; + + ReaderModeDialog(@NonNull Context context, @NonNull MangaHistory history) { + mBuilder = new AlertDialog.Builder(context); + mBuilder.setTitle(R.string.reader_mode); + mBuilder.setSingleChoiceItems(R.array.reader_modes, history.readerPreset, this); + mBuilder.setNegativeButton(R.string.close, null); + mBuilder.setCancelable(true); + } + + ReaderModeDialog setListener(@Nullable OnReaderModeChangeListener listener) { + mListener = listener; + return this; + } + + public void show() { + mBuilder.create().show(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mListener != null) { + mListener.onReaderModeChanged((short) which); + } + dialog.dismiss(); + } + + interface OnReaderModeChangeListener { + + void onReaderModeChanged(short mode); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ReaderStatusBar.java b/app/src/main/java/org/nv95/openmanga/reader/ReaderStatusBar.java new file mode 100644 index 00000000..402dde17 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ReaderStatusBar.java @@ -0,0 +1,173 @@ +package org.nv95.openmanga.reader; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.graphics.ColorUtils; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.ThemeUtils; + +import java.util.Calendar; + +public final class ReaderStatusBar extends LinearLayout { + + private final TextView mTextViewStatus; + private final TextView mTextViewClock; + private final TextView mTextViewBattery; + private final ImageView mImageViewBattery; + + private final Calendar mTime; + private boolean mIsActive = false; + + private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() { + @SuppressLint("SetTextI18n") + @Override + public void onReceive(Context ctxt, Intent intent) { + update(intent); + } + }; + + public ReaderStatusBar(Context context) { + this(context, null, 0); + } + + public ReaderStatusBar(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public ReaderStatusBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + View.inflate(context, R.layout.view_status, this); + setOrientation(HORIZONTAL); + int background = ThemeUtils.getThemeAttrColor(context, android.R.attr.windowBackground); + background = ColorUtils.setAlphaComponent(background, 100); + setBackgroundColor(background); + + mTextViewStatus = findViewById(R.id.textView_status); + mTextViewBattery = findViewById(R.id.textView_battery); + mTextViewClock = findViewById(R.id.textView_clock); + mImageViewBattery = findViewById(R.id.imageView_battery); + + mTime = Calendar.getInstance(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updateBattery(); + mTicker.run(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getHandler().removeCallbacks(mTicker); + getContext().unregisterReceiver(mBatteryReceiver); + } + + private void updateBattery() { + final IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + final Intent batteryStatus = getContext().registerReceiver(mBatteryReceiver, filter); + if (batteryStatus != null) { + update(batteryStatus); + } + } + + @SuppressLint("SetTextI18n") + private void update(@NonNull final Intent batteryStatus) { + int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || + status == BatteryManager.BATTERY_STATUS_FULL; + + int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + + int batteryPct = level * 100 / scale; + + mTextViewBattery.setText(batteryPct + "%"); + int res; + if (isCharging) { + if (batteryPct >=90) { + res = R.drawable.ic_battery_charging_90_black; + } else if (batteryPct >= 50) { + res = R.drawable.ic_battery_charging_60_black; + } else if (batteryPct >= 30) { + res = R.drawable.ic_battery_charging_40_black; + } else { + res = R.drawable.ic_battery_charging_20_black; + } + } else { + if (batteryPct >=90) { + res = R.drawable.ic_battery_80_black; + } else if (batteryPct >= 70) { + res = R.drawable.ic_battery_60_black; + } else if (batteryPct >= 35) { + res = R.drawable.ic_battery_40_black; + } else if (batteryPct >= 15) { + res = R.drawable.ic_battery_20_black; + } else { + res = R.drawable.ic_battery_alert_black; + } + } + mImageViewBattery.setImageResource(res); + } + + private final Runnable mTicker = new Runnable() { + public void run() { + onTimeChanged(); + + long now = SystemClock.uptimeMillis(); + long next = now + (60000 - now % 60000); + + getHandler().postAtTime(mTicker, next); + } + }; + + private void onTimeChanged() { + if (isActive()) { + mTime.setTimeInMillis(System.currentTimeMillis()); + mTextViewClock.setText(DateFormat.format("k:mm", mTime)); + } + } + + private boolean isActive() { + return mIsActive && getVisibility() == VISIBLE; + } + + @SuppressLint("DefaultLocale") + public void setStatus(int current, int total) { + mTextViewStatus.setText(String.format("%d/%d", current, total)); + } + + public void show() { + if (mIsActive) { + onTimeChanged(); + AnimationUtils.setVisibility(this, View.VISIBLE); + } + } + + public void hide() { + AnimationUtils.setVisibility(this, View.GONE); + } + + public void setIsActive(boolean flag) { + mIsActive = flag; + if (!flag) { + hide(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/ResumeReadingTask.java b/app/src/main/java/org/nv95/openmanga/reader/ResumeReadingTask.java new file mode 100644 index 00000000..0b777760 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ResumeReadingTask.java @@ -0,0 +1,64 @@ +package org.nv95.openmanga.reader; + +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.core.ObjectWrapper; +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.storage.db.HistoryRepository; + +/** + * Created by koitharu on 29.01.18. + */ + +final class ResumeReadingTask extends WeakAsyncTask> { + + ResumeReadingTask(ReaderActivity readerActivity) { + super(readerActivity); + } + + @Override + protected ObjectWrapper doInBackground(MangaHeader... mangaHeaders) { + try { + final ReaderActivity.Result result = new ReaderActivity.Result(); + final MangaHeader manga = mangaHeaders[0]; + final MangaHistory history = manga instanceof MangaHistory ? (MangaHistory) manga : + HistoryRepository.get(getObject()).find(manga); + MangaProvider provider = MangaProvider.get(getObject(), manga.provider); + result.mangaDetails = manga instanceof MangaDetails ? (MangaDetails) manga : provider.getDetails(history); + result.chapter = history == null ? result.mangaDetails.chapters.get(0) : + CollectionsUtils.findItemById(result.mangaDetails.chapters, history.chapterId); + result.pageId = history != null ? history.pageId : 0; + return new ObjectWrapper<>(result); + } catch (Exception e) { + e.printStackTrace(); + return new ObjectWrapper<>(e); + } + } + + @Override + protected void onPostExecute(@NonNull ReaderActivity readerActivity, ObjectWrapper result) { + super.onPostExecute(readerActivity, result); + if (result.isSuccess()) { + final ReaderActivity.Result data = result.get(); + readerActivity.onMangaReady(data); + } else { + new AlertDialog.Builder(readerActivity) + .setMessage(ErrorUtils.getErrorMessageDetailed(readerActivity, result.getError())) + .setCancelable(true) + .setOnCancelListener(readerActivity) + .setNegativeButton(R.string.close, readerActivity) + .create() + .show(); + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/reader/ToolButtonCompat.java b/app/src/main/java/org/nv95/openmanga/reader/ToolButtonCompat.java new file mode 100644 index 00000000..61486a6d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/ToolButtonCompat.java @@ -0,0 +1,30 @@ +package org.nv95.openmanga.reader; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.widget.ImageView; + +import org.nv95.openmanga.common.utils.ThemeUtils; + +/** + * Created by koitharu on 08.01.18. + */ + +public final class ToolButtonCompat extends AppCompatImageView { + + public ToolButtonCompat(Context context) { + this(context, null, 0); + } + + public ToolButtonCompat(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public ToolButtonCompat(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setBackgroundDrawable(ThemeUtils.getSelectableBackgroundBorderless(context)); + setScaleType(ImageView.ScaleType.CENTER); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/decoder/ImageConverter.java b/app/src/main/java/org/nv95/openmanga/reader/decoder/ImageConverter.java new file mode 100644 index 00000000..94111434 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/decoder/ImageConverter.java @@ -0,0 +1,86 @@ +package org.nv95.openmanga.reader.decoder; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.support.annotation.NonNull; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by koitharu on 09.01.18. + */ + +public final class ImageConverter { + + private static final ImageConverter sInstance = new ImageConverter(); + private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(); + + @NonNull + public static ImageConverter getInstance() { + return sInstance; + } + + private ImageConverter() { + } + + public void convert(@NonNull String filename, @NonNull Callback callback) { + new ConvertTask(callback).executeOnExecutor(sExecutor, filename); + } + + private static class ConvertTask extends AsyncTask { + + private final Callback mCallback; + + ConvertTask(Callback callback) { + mCallback = callback; + } + + @Override + protected String doInBackground(String... strings) { + FileOutputStream out = null; + Bitmap bitmap = null; + try { + final String filename = strings[0]; + bitmap = BitmapFactory.decodeFile(filename); + out = new FileOutputStream(filename); + bitmap.compress(Bitmap.CompressFormat.JPEG, 95, out); + return filename; + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (bitmap != null) { + bitmap.recycle(); + } + if (out != null) { + try { + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + } + + @Override + protected void onPostExecute(String s) { + super.onPostExecute(s); + if (s == null) { + mCallback.onImageConvertFailed(); + } else { + mCallback.onImageConverted(); + } + } + } + + public interface Callback { + + void onImageConverted(); + + void onImageConvertFailed(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/loader/PageDownloader.java b/app/src/main/java/org/nv95/openmanga/reader/loader/PageDownloader.java new file mode 100644 index 00000000..b9570bef --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/loader/PageDownloader.java @@ -0,0 +1,59 @@ +package org.nv95.openmanga.reader.loader; + +import android.content.Context; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.LongSparseArray; + +import org.nv95.openmanga.core.models.MangaPage; + +import java.io.File; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by koitharu on 11.01.18. + */ + +public final class PageDownloader { + + @Nullable + private static PageDownloader sInstance = null; + + @NonNull + public static PageDownloader getInstance() { + if (sInstance == null) { + sInstance = new PageDownloader(); + } + return sInstance; + } + + private final LongSparseArray mTasksMap; + private final ExecutorService mExecutor; + + private PageDownloader() { + mTasksMap = new LongSparseArray<>(5); + mExecutor = Executors.newFixedThreadPool(3); + } + + public void downloadPage(Context context, MangaPage page, File destination, PageLoadCallback callback) { + PageLoadTask task = mTasksMap.get(page.id); + if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { + task.cancel(false); + } + task = new PageLoadTask(context, callback); + task.executeOnExecutor(mExecutor, new PageLoadRequest(page, destination)); + mTasksMap.put(page.id, task); + } + + public void cancel(MangaPage page) { + PageLoadTask task = mTasksMap.get(page.id); + if (task != null) { + if (task.getStatus() != AsyncTask.Status.FINISHED) { + task.cancel(false); + } + } + mTasksMap.remove(page.id); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadCallback.java b/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadCallback.java new file mode 100644 index 00000000..d1317c91 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadCallback.java @@ -0,0 +1,14 @@ +package org.nv95.openmanga.reader.loader; + +/** + * Created by koitharu on 09.01.18. + */ + +public interface PageLoadCallback { + + void onPageDownloaded(); + + void onPageDownloadFailed(Throwable reason); + + void onPageDownloadProgress(int progress, int max); +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadRequest.java b/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadRequest.java new file mode 100644 index 00000000..a5ef5b90 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadRequest.java @@ -0,0 +1,20 @@ +package org.nv95.openmanga.reader.loader; + +import org.nv95.openmanga.core.models.MangaPage; + +import java.io.File; + +/** + * Created by koitharu on 19.01.18. + */ + +final class PageLoadRequest { + + public final MangaPage page; + public final File destination; + + PageLoadRequest(MangaPage page, File destination) { + this.page = page; + this.destination = destination; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadTask.java b/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadTask.java new file mode 100644 index 00000000..af88c0a7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/loader/PageLoadTask.java @@ -0,0 +1,131 @@ +package org.nv95.openmanga.reader.loader; + +import android.content.Context; +import android.os.AsyncTask; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.utils.network.HttpException; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.providers.ZipArchiveProvider; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; + +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by koitharu on 19.01.18. + */ + +public final class PageLoadTask extends AsyncTask { + + private final WeakReference mContextRef; + private final PageLoadCallback mCallback; + private final Object mPauseLock = new Object(); + private boolean mIsPaused = false; + + public PageLoadTask(Context context, PageLoadCallback callback) { + mContextRef = new WeakReference<>(context); + mCallback = callback; + } + + @Override + protected Throwable doInBackground(PageLoadRequest... params) { + final PageLoadRequest param = params[0]; + InputStream input = null; + FileOutputStream output = null; + try { + final MangaProvider provider = MangaProvider.get(getContext(), param.page.provider); + final String pageUrl = provider.getImageUrl(param.page); + if (pageUrl.startsWith(ZipArchiveProvider.SCHEME)) { + ZipArchiveProvider.extractTo(pageUrl, param.destination); + return null; + } + final String domain = MangaProvider.getDomain(param.page.provider); + final Request request = new Request.Builder() + .url(pageUrl) + .header(NetworkUtils.HEADER_USER_AGENT, NetworkUtils.USER_AGENT_DEFAULT) + .header(NetworkUtils.HEADER_REFERER, "http://" + domain) + .get() + .build(); + final Response response = NetworkUtils.getHttpClient().newCall(request).execute(); + if (!response.isSuccessful()) { + return new HttpException(response.code()); + } + input = response.body().byteStream(); + output = new FileOutputStream(param.destination); + final int contentLength = NetworkUtils.getContentLength(response); + final byte[] buffer = new byte[512]; + int total = 0; + int length; + while ((length = input.read(buffer)) >= 0) { + output.write(buffer, 0, length); + total += length; + if (contentLength > 0) { + publishProgress(total, contentLength); + } + synchronized (mPauseLock) { + while (mIsPaused && !isCancelled()) { + try { + mPauseLock.wait(); + } catch (InterruptedException e) { + return e; + } + } + } + if (isCancelled()) { + output.close(); + output = null; + param.destination.delete(); + return null; + } + } + output.flush(); + return null; + } catch (Throwable e) { + e.printStackTrace(); + return e; + } finally { + if (input != null) try { + input.close(); + } catch (IOException ignored) { + } + if (output != null) try { + output.close(); + } catch (IOException ignored) { + } + } + } + + @Override + protected void onProgressUpdate(Integer[] values) { + mCallback.onPageDownloadProgress(values[0], values[1]); + } + + @Override + protected void onPostExecute(Throwable throwable) { + if (throwable == null) { + mCallback.onPageDownloaded(); + } else { + mCallback.onPageDownloadFailed(throwable); + } + } + + @Nullable + private Context getContext() { + return mContextRef.get(); + } + + public void setPause(boolean isPause) { + synchronized(mPauseLock) { + mIsPaused = isPause; + if (!mIsPaused) { + mPauseLock.notifyAll(); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/loader/PagesCache.java b/app/src/main/java/org/nv95/openmanga/reader/loader/PagesCache.java new file mode 100644 index 00000000..ac30473d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/loader/PagesCache.java @@ -0,0 +1,101 @@ +package org.nv95.openmanga.reader.loader; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; + +import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; +import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator; + +import java.io.File; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Created by koitharu on 09.01.18. + */ + +public final class PagesCache { + + private static final Comparator FILES_COMPARATOR = new Comparator() { + @Override + public int compare(File o1, File o2) { + return compare(o1.lastModified(), o2.lastModified()); + } + + private int compare(long x, long y) { + return Long.compare(x, y); + } + }; + + @Nullable + private static PagesCache sInstance = null; + + @NonNull + public static PagesCache getInstance(Context context) { + if (sInstance == null) { + sInstance = new PagesCache(context); + } + return sInstance; + } + + private final File mCacheDir; + private final FileNameGenerator mNameGenerator; + + private PagesCache(Context context) { + File cacheDir = context.getExternalCacheDir(); + if (cacheDir == null) { + cacheDir = context.getCacheDir(); + } + mCacheDir = new File(cacheDir,"pages"); + //noinspection ResultOfMethodCallIgnored + mCacheDir.mkdirs(); + mNameGenerator = new HashCodeFileNameGenerator(); + } + + @NonNull + public File getFileForUrl(String url) { + String filename = mNameGenerator.generate(url); + return new File(mCacheDir, filename); + } + + @WorkerThread + public long getTotalSize() { + final File[] files = mCacheDir.listFiles(); + long size = 0; + for (File o : files) { + if (o != null && o.exists()) { + size += o.length(); + } + } + return size; + } + + @WorkerThread + public long trimToSize(long maxSize) { + final File[] files = mCacheDir.listFiles(); + Arrays.sort(files, FILES_COMPARATOR); + long size = 0; + for (File o : files) { + if (o != null && o.exists()) { + size += o.length(); + } + } + if (size <= maxSize) { + return size; + } + for (File o : files) { + if (o != null && o.exists()) { + long fileSize = o.length(); + if (o.delete()) { + size -= fileSize; + } + if (size <= maxSize) { + break; + } + } + } + return size; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/pager/OverScrollPager.java b/app/src/main/java/org/nv95/openmanga/reader/pager/OverScrollPager.java new file mode 100644 index 00000000..fae7e7c7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/pager/OverScrollPager.java @@ -0,0 +1,94 @@ +package org.nv95.openmanga.reader.pager; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import org.nv95.openmanga.reader.OnOverScrollListener; + +/** + * Created by koitharu on 30.01.18. + */ + +final class OverScrollPager extends ViewPager { + + private static final float SWIPE_TOLERANCE = .25f; + + private float mStartX; + @Nullable + private OnOverScrollListener mOverScrollListener; + + public OverScrollPager(@NonNull Context context) { + this(context, null); + } + + public OverScrollPager(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public void setOnOverScrollListener(@Nullable OnOverScrollListener listener) { + mOverScrollListener = listener; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + try { + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN && ev.getPointerCount() == 1) { + final int currentItem = getCurrentItem(); + if (currentItem == 0 || currentItem == getItemCount() - 1) { + mStartX = ev.getX(); + } + } + + return super.onInterceptTouchEvent(ev); + } catch (IllegalArgumentException e) { + return false; + } + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent ev) { + if (mOverScrollListener == null || ev.getPointerCount() != 1) { + return super.onTouchEvent(ev); + } + final int currentItem = getCurrentItem(); + try { + if (currentItem == 0) { + if (ev.getActionMasked() == MotionEvent.ACTION_UP) { + float displacement = ev.getX() - mStartX; + + if (ev.getX() > mStartX && displacement > getMeasuredWidth() * SWIPE_TOLERANCE) { + mOverScrollListener.onOverScrolledStart(); + return true; + } + mStartX = 0f; + } + } else if (currentItem == getItemCount() - 1) { + if (ev.getActionMasked() == MotionEvent.ACTION_UP) { + float displacement = mStartX - ev.getX(); + + if (ev.getX() < mStartX && displacement > getMeasuredWidth() * SWIPE_TOLERANCE) { + mOverScrollListener.onOverScrolledEnd(); + return true; + } + mStartX = 0f; + } + } + + return super.onTouchEvent(ev); + } catch (IllegalArgumentException e) { + return false; + } + } + + public int getItemCount() { + PagerAdapter adapter = getAdapter(); + return adapter == null ? 0 : adapter.getCount(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/pager/PageView.java b/app/src/main/java/org/nv95/openmanga/reader/pager/PageView.java new file mode 100644 index 00000000..e224112b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/pager/PageView.java @@ -0,0 +1,183 @@ +package org.nv95.openmanga.reader.pager; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.View; +import android.view.ViewStub; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.davemorrissey.labs.subscaleview.ImageSource; +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.views.TextProgressView; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.reader.decoder.ImageConverter; +import org.nv95.openmanga.reader.loader.PageDownloader; +import org.nv95.openmanga.reader.loader.PageLoadCallback; +import org.nv95.openmanga.reader.loader.PagesCache; + +import java.io.File; + +/** + * Created by koitharu on 09.01.18. + */ + +public final class PageView extends FrameLayout implements View.OnClickListener, + ImageConverter.Callback, PageLoadCallback { + + private MangaPage mPage; + private File mFile; + + private final SubsamplingScaleImageView mSubsamplingScaleImageView; + private final TextProgressView mTextProgressView; + @Nullable + private ViewStub mStubError; + @Nullable + private View mErrorView = null; + + public PageView(@NonNull Context context) { + this(context, null, 0); + } + + public PageView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public PageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + View.inflate(context, R.layout.view_page, this); + mSubsamplingScaleImageView = findViewById(R.id.subsamplingImageView); + mTextProgressView = findViewById(R.id.progressView); + mStubError = findViewById(R.id.stubError); + mSubsamplingScaleImageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED); + mSubsamplingScaleImageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE); + mSubsamplingScaleImageView.setMinimumDpi(90); + mSubsamplingScaleImageView.setMinimumTileDpi(180); + mSubsamplingScaleImageView.setOnImageEventListener(new SubsamplingScaleImageView.DefaultOnImageEventListener() { + + @Override + public void onReady() { + onLoadingComplete(); + } + + @Override + public void onImageLoadError(Exception e) { + onImageDisplayFailed(e); + } + }); + } + + @SuppressLint("ClickableViewAccessibility") + public void setTapDetector(final GestureDetector detector) { + mSubsamplingScaleImageView.setOnTouchListener((v, event) -> detector.onTouchEvent(event)); + } + + public void setData(MangaPage page) { + mTextProgressView.setProgress(TextProgressView.INDETERMINATE); + mTextProgressView.setVisibility(VISIBLE); + setError(null); + mPage = page; + if (page.url.startsWith("file://")) { + mSubsamplingScaleImageView.setImage(ImageSource.uri(page.url)); + } else { + mFile = PagesCache.getInstance(getContext()).getFileForUrl(page.url); + if (mFile.exists()) { + mSubsamplingScaleImageView.setImage(ImageSource.uri(Uri.fromFile(mFile))); + } else { + PageDownloader.getInstance().downloadPage(getContext(), page, mFile, this); + } + } + } + + private void setError(@Nullable CharSequence errorMessage) { + if (errorMessage == null) { + if (mErrorView != null) { + mErrorView.setVisibility(GONE); + } + return; + } + mTextProgressView.setVisibility(GONE); + if (mErrorView == null) { + assert mStubError != null; + mErrorView = mStubError.inflate(); + mErrorView.findViewById(R.id.button_retry).setOnClickListener(this); + mStubError = null; + } + mErrorView.setVisibility(VISIBLE); + ((TextView)mErrorView.findViewById(R.id.textView_error)).setText(errorMessage); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.button_retry: + setError(null); + mTextProgressView.setVisibility(VISIBLE); + mFile.delete(); + PageDownloader.getInstance().downloadPage(getContext(), mPage, mFile, this); + break; + } + } + + /** + * Loading done, alles ok + */ + public void onLoadingComplete() { + setError(null); + mTextProgressView.setVisibility(GONE); + } + + public void onImageDisplayFailed(Exception e) { + if (mFile.exists()) { + ImageConverter.getInstance().convert(mFile.getPath(), this); + } else { + mTextProgressView.setVisibility(GONE); + setError(ErrorUtils.getErrorMessageDetailed(getContext(), e)); + } + } + + @Override + public void onImageConverted() { + mSubsamplingScaleImageView.setImage(ImageSource.uri(Uri.fromFile(mFile))); + } + + @Override + public void onImageConvertFailed() { + setError(getContext().getString(R.string.image_decode_error)); + } + + @Override + public void onPageDownloaded() { + mSubsamplingScaleImageView.setImage(ImageSource.uri(Uri.fromFile(mFile))); + } + + @Override + public void onPageDownloadFailed(Throwable reason) { + setError(ErrorUtils.getErrorMessageDetailed(getContext(), reason)); + } + + @Override + public void onPageDownloadProgress(int progress, int max) { + mTextProgressView.setProgress(progress, max); + } + + public void recycle() { + if (mPage != null) { + PageDownloader.getInstance().cancel(mPage); + } + setError(null); + mSubsamplingScaleImageView.recycle(); + } + + public MangaPage getData() { + return mPage; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/pager/PagerReaderAdapter.java b/app/src/main/java/org/nv95/openmanga/reader/pager/PagerReaderAdapter.java new file mode 100644 index 00000000..3dd1ca8d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/pager/PagerReaderAdapter.java @@ -0,0 +1,54 @@ +package org.nv95.openmanga.reader.pager; + +import android.support.annotation.NonNull; +import android.support.v4.view.PagerAdapter; +import android.view.GestureDetector; +import android.view.ViewGroup; + +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +/** + * Created by koitharu on 09.01.18. + */ + +class PagerReaderAdapter extends RecyclerPagerAdapter { + + private final ArrayList mDataset; + private final GestureDetector mGestureDetector; + + PagerReaderAdapter(ArrayList dataset, GestureDetector gestureDetector) { + mGestureDetector = gestureDetector; + mDataset = dataset; + } + + @Override + protected PageView onCreateView(@NonNull ViewGroup container) { + final PageView pageView = new PageView(container.getContext()); + pageView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + pageView.setTapDetector(mGestureDetector); + return pageView; + } + + @Override + protected void onBindView(@NonNull PageView view, int position) { + view.setData(mDataset.get(position)); + } + + @Override + protected void onRecyclerView(@NonNull PageView view) { + view.recycle(); + } + + @Override + public int getCount() { + return mDataset.size(); + } + + @Override + public int getItemPosition(@NonNull Object object) { + PageView view = (PageView) object; + return mDataset.contains(view.getData()) ? PagerAdapter.POSITION_UNCHANGED : PagerAdapter.POSITION_NONE; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/pager/PagerReaderFragment.java b/app/src/main/java/org/nv95/openmanga/reader/pager/PagerReaderFragment.java new file mode 100644 index 00000000..2a7dac62 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/pager/PagerReaderFragment.java @@ -0,0 +1,147 @@ +package org.nv95.openmanga.reader.pager; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.view.ViewPager; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.reader.OnOverScrollListener; +import org.nv95.openmanga.reader.ReaderFragment; + +import java.util.List; + +/** + * Created by koitharu on 09.01.18. + */ + +public class PagerReaderFragment extends ReaderFragment implements ViewPager.OnPageChangeListener { + + protected OverScrollPager mPager; + protected PagerReaderAdapter mAdapter; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_reader_pager, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + mPager = view.findViewById(R.id.pager); + mAdapter = onCreateAdapter(new GestureDetector(view.getContext(), new TapDetector())); + mPager.setOffscreenPageLimit(2); + mPager.setAdapter(mAdapter); + mPager.addOnPageChangeListener(this); + } + + protected PagerReaderAdapter onCreateAdapter(GestureDetector gestureDetector) { + return new PagerReaderAdapter(getPages(), gestureDetector); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Activity activity = getActivity(); + if (activity instanceof OnOverScrollListener) { + mPager.setOnOverScrollListener((OnOverScrollListener) activity); + } + } + + @Override + public void setPages(List pages) { + super.setPages(pages); + mAdapter.notifyDataSetChanged(); + } + + @Override + public int getCurrentPageIndex() { + return mPager.getCurrentItem(); + } + + @Override + public void scrollToPage(int index) { + mPager.setCurrentItem(index, false); + } + + @Override + public void smoothScrollToPage(int index) { + mPager.setCurrentItem(index, true); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + onPageChanged(position); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + + @Override + public boolean moveLeft() { + return movePrevious(); + } + + @Override + public boolean moveRight() { + return moveNext(); + } + + @Override + public boolean moveUp() { + return false; + } + + @Override + public boolean moveDown() { + return false; + } + + private final class TapDetector extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (mPager == null) { + return false; + } + final float x = e.getX(); + final float w3 = mPager.getWidth() / 3; + if (x < w3) { + moveLeft(); + } else if (x <= w3 + w3) { + final float y = e.getY(); + final float h3 = mPager.getHeight() / 3; + if (y < h3) { + moveLeft(); + } else if (y <= h3 + h3) { + toggleUi(); + } else { + moveRight(); + } + } else { + moveRight(); + } + return true; + } + } + + @Override + public void onRestoreState(@NonNull Bundle savedState) { + super.onRestoreState(savedState); + onPageSelected(mPager.getCurrentItem()); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/pager/RecyclerPagerAdapter.java b/app/src/main/java/org/nv95/openmanga/reader/pager/RecyclerPagerAdapter.java new file mode 100644 index 00000000..4f83120e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/pager/RecyclerPagerAdapter.java @@ -0,0 +1,49 @@ +package org.nv95.openmanga.reader.pager; + +import android.support.annotation.NonNull; +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; + +import java.util.Stack; + +/** + * Created by koitharu on 11.01.18. + */ + +@SuppressWarnings("unchecked") +abstract class RecyclerPagerAdapter extends PagerAdapter { + + private final Stack mViewPool; + + public RecyclerPagerAdapter() { + mViewPool = new Stack<>(); + } + + @NonNull + @Override + public final Object instantiateItem(@NonNull ViewGroup container, int position) { + final T view = mViewPool.isEmpty() ? onCreateView(container) : mViewPool.pop(); + onBindView(view, position); + container.addView(view); + return view; + } + + @Override + public final void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + container.removeView((View) object); + onRecyclerView((T) object); + mViewPool.push((T) object); + } + + @Override + public final boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } + + protected abstract T onCreateView(@NonNull ViewGroup container); + + protected abstract void onBindView(@NonNull T view, int position); + + protected abstract void onRecyclerView(@NonNull T view); +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/pager/RtlPagerReaderAdapter.java b/app/src/main/java/org/nv95/openmanga/reader/pager/RtlPagerReaderAdapter.java new file mode 100644 index 00000000..c0d69123 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/pager/RtlPagerReaderAdapter.java @@ -0,0 +1,24 @@ +package org.nv95.openmanga.reader.pager; + +import android.support.annotation.NonNull; +import android.view.GestureDetector; + +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +/** + * Created by koitharu on 06.02.18. + */ + +final class RtlPagerReaderAdapter extends PagerReaderAdapter { + + RtlPagerReaderAdapter(ArrayList dataset, GestureDetector gestureDetector) { + super(dataset, gestureDetector); + } + + @Override + protected void onBindView(@NonNull PageView view, int position) { + super.onBindView(view, getCount() - position - 1); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/pager/RtlPagerReaderFragment.java b/app/src/main/java/org/nv95/openmanga/reader/pager/RtlPagerReaderFragment.java new file mode 100644 index 00000000..74a3d740 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/pager/RtlPagerReaderFragment.java @@ -0,0 +1,49 @@ +package org.nv95.openmanga.reader.pager; + +import android.view.GestureDetector; + +/** + * Created by koitharu on 06.02.18. + */ + +public class RtlPagerReaderFragment extends PagerReaderFragment { + + @Override + protected PagerReaderAdapter onCreateAdapter(GestureDetector gestureDetector) { + return new RtlPagerReaderAdapter(mPages, gestureDetector); + } + + @Override + public int getCurrentPageIndex() { + return invertIndex(super.getCurrentPageIndex()); + } + + @Override + public void scrollToPage(int index) { + super.scrollToPage(invertIndex(index)); + } + + @Override + public void smoothScrollToPage(int index) { + super.smoothScrollToPage(invertIndex(index)); + } + + @Override + public void onPageChanged(int page) { + super.onPageChanged(invertIndex(page)); + } + + private int invertIndex(int index) { + return mPages.size() - index - 1; + } + + @Override + public boolean moveRight() { + return super.moveLeft(); + } + + @Override + public boolean moveLeft() { + return super.moveRight(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/thumbview/OnThumbnailClickListener.java b/app/src/main/java/org/nv95/openmanga/reader/thumbview/OnThumbnailClickListener.java new file mode 100644 index 00000000..fadee437 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/thumbview/OnThumbnailClickListener.java @@ -0,0 +1,10 @@ +package org.nv95.openmanga.reader.thumbview; + +/** + * Created by koitharu on 13.01.18. + */ + +public interface OnThumbnailClickListener { + + void onThumbnailClick(int position); +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/thumbview/ThumbViewAdapter.java b/app/src/main/java/org/nv95/openmanga/reader/thumbview/ThumbViewAdapter.java new file mode 100644 index 00000000..25d66200 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/thumbview/ThumbViewAdapter.java @@ -0,0 +1,94 @@ +package org.nv95.openmanga.reader.thumbview; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.MetricsUtils; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.reader.loader.PagesCache; + +import java.io.File; +import java.util.ArrayList; + +/** + * Created by koitharu on 13.01.18. + */ + +final class ThumbViewAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private final OnThumbnailClickListener mClickListener; + private final PagesCache mCache; + + ThumbViewAdapter(Context context, ArrayList pages, OnThumbnailClickListener onClickListener) { + mDataset = pages; + mClickListener = onClickListener; + mCache = PagesCache.getInstance(context); + setHasStableIds(true); + } + + @Override + public ThumbHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ThumbHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_thumb, parent, false)); + } + + @Override + public void onBindViewHolder(ThumbHolder holder, int position) { + holder.bind(mDataset.get(position)); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + final class ThumbHolder extends DataViewHolder implements View.OnClickListener { + + private final ImageView mImageView; + private final TextView mTextView; + private final View mSelector; + + ThumbHolder(View itemView) { + super(itemView); + mImageView = itemView.findViewById(R.id.imageView); + mTextView = itemView.findViewById(R.id.textView); + mSelector = itemView.findViewById(R.id.selector); + mSelector.setOnClickListener(this); + } + + @Override + public void bind(MangaPage mangaPage) { + super.bind(mangaPage); + final MetricsUtils.Size size = MetricsUtils.getPreferredCellSizeMedium(getContext().getResources()); + mTextView.setText(String.valueOf(getAdapterPosition() + 1)); + final File file = mCache.getFileForUrl(mangaPage.url); + if (file.exists()) { + ImageUtils.setThumbnailCropped(mImageView, mCache.getFileForUrl(mangaPage.url), size); + } else if (NetworkUtils.isNetworkAvailable(getContext(), false)) { + ImageUtils.setThumbnailCropped(mImageView, mangaPage.url, size, MangaProvider.getDomain(mangaPage.provider)); + } else { + ImageUtils.setEmptyThumbnail(mImageView); + } + } + + @Override + public void onClick(View v) { + mClickListener.onThumbnailClick(getAdapterPosition()); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/thumbview/ThumbViewFragment.java b/app/src/main/java/org/nv95/openmanga/reader/thumbview/ThumbViewFragment.java new file mode 100644 index 00000000..5184188f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/thumbview/ThumbViewFragment.java @@ -0,0 +1,70 @@ +package org.nv95.openmanga.reader.thumbview; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.dialogs.AppBaseBottomSheetDialogFragment; +import org.nv95.openmanga.common.utils.MetricsUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.views.recyclerview.SpaceItemDecoration; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +/** + * Created by koitharu on 13.01.18. + */ + +public final class ThumbViewFragment extends AppBaseBottomSheetDialogFragment implements OnThumbnailClickListener { + + private RecyclerView mRecyclerView; + private ArrayList mPages; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Bundle args = getArguments(); + assert args != null; + mPages = args.getParcelableArrayList("pages"); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.recyclerview, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = view.findViewById(R.id.recyclerView); + final int spanCount = MetricsUtils.getPreferredColumnsCountMedium(view.getResources()); + mRecyclerView.setLayoutManager(new GridLayoutManager(view.getContext(), spanCount)); + mRecyclerView.addItemDecoration(new SpaceItemDecoration(ResourceUtils.dpToPx(view.getResources(), 4))); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Activity activity = getActivity(); + ThumbViewAdapter adapter = new ThumbViewAdapter(activity, mPages, this); + mRecyclerView.setAdapter(adapter); + } + + @Override + public void onThumbnailClick(int position) { + final Activity activity = getActivity(); + if (activity != null && activity instanceof OnThumbnailClickListener) { + ((OnThumbnailClickListener) activity).onThumbnailClick(position); + dismiss(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonImageView.java b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonImageView.java new file mode 100644 index 00000000..41f301a8 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonImageView.java @@ -0,0 +1,31 @@ +package org.nv95.openmanga.reader.webtoon; + +import android.content.Context; +import android.graphics.PointF; +import android.util.AttributeSet; + +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; + +public final class WebtoonImageView extends SubsamplingScaleImageView { + + public WebtoonImageView(Context context) { + this(context, null); + } + + public WebtoonImageView(Context context, AttributeSet attr) { + super(context, attr); + } + + @Override + protected void onReady() { + setupScale(); + } + + void setupScale() { + setMinScale(getWidth() / (float) getSWidth()); + setScaleAndCenter( + getMinScale(), + new PointF(getSWidth() / 2f, 0) + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonPageHolder.java b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonPageHolder.java new file mode 100644 index 00000000..5778a29e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonPageHolder.java @@ -0,0 +1,160 @@ +package org.nv95.openmanga.reader.webtoon; + +import android.net.Uri; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewStub; +import android.widget.TextView; + +import com.davemorrissey.labs.subscaleview.ImageSource; +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.views.TextProgressView; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.reader.decoder.ImageConverter; +import org.nv95.openmanga.reader.loader.PageDownloader; +import org.nv95.openmanga.reader.loader.PageLoadCallback; +import org.nv95.openmanga.reader.loader.PagesCache; + +import java.io.File; + +final class WebtoonPageHolder extends DataViewHolder implements View.OnClickListener, + ImageConverter.Callback, PageLoadCallback { + + private File mFile; + + private final WebtoonImageView mWebtoonImageView; + private final TextProgressView mTextProgressView; + @Nullable + private ViewStub mStubError; + @Nullable + private View mErrorView = null; + + public WebtoonPageHolder(View itemView) { + super(itemView); + mWebtoonImageView = itemView.findViewById(R.id.webtoonImageView); + mTextProgressView = itemView.findViewById(R.id.progressView); + mStubError = itemView.findViewById(R.id.stubError); + mWebtoonImageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED); + mWebtoonImageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE); + mWebtoonImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); + mWebtoonImageView.setMinimumDpi(90); + mWebtoonImageView.setMinimumTileDpi(180); + mWebtoonImageView.setOnImageEventListener(new WebtoonImageView.DefaultOnImageEventListener() { + + @Override + public void onReady() { + onLoadingComplete(); + } + + @Override + public void onImageLoadError(Exception e) { + onImageDisplayFailed(e); + } + }); + } + + @Override + public void bind(MangaPage page) { + super.bind(page); + mTextProgressView.setProgress(TextProgressView.INDETERMINATE); + mTextProgressView.setVisibility(View.VISIBLE); + setError(null); + if (page.url.startsWith("file://")) { + mWebtoonImageView.setImage(ImageSource.uri(page.url)); + } else { + mFile = PagesCache.getInstance(getContext()).getFileForUrl(page.url); + if (mFile.exists()) { + mWebtoonImageView.setImage(ImageSource.uri(Uri.fromFile(mFile))); + } else { + PageDownloader.getInstance().downloadPage(getContext(), page, mFile, this); + } + } + } + + private void setError(@Nullable CharSequence errorMessage) { + if (errorMessage == null) { + if (mErrorView != null) { + mErrorView.setVisibility(View.GONE); + } + return; + } + mTextProgressView.setVisibility(View.GONE); + if (mErrorView == null) { + assert mStubError != null; + mErrorView = mStubError.inflate(); + mErrorView.findViewById(R.id.button_retry).setOnClickListener(this); + mStubError = null; + } + mErrorView.setVisibility(View.VISIBLE); + ((TextView)mErrorView.findViewById(R.id.textView_error)).setText(errorMessage); + } + + @Override + public void onClick(View v) { + final MangaPage page = getData(); + if (page == null) { + return; + } + switch (v.getId()) { + case R.id.button_retry: + setError(null); + mTextProgressView.setVisibility(View.VISIBLE); + mFile.delete(); + PageDownloader.getInstance().downloadPage(v.getContext(), page, mFile, this); + break; + } + } + + public void onLoadingComplete() { + setError(null); + mTextProgressView.setVisibility(View.GONE); + } + + public void onImageDisplayFailed(Exception e) { + if (mFile.exists()) { + ImageConverter.getInstance().convert(mFile.getPath(), this); + } else { + mTextProgressView.setVisibility(View.GONE); + setError(ErrorUtils.getErrorMessageDetailed(itemView.getContext(), e)); + } + } + + @Override + public void onImageConverted() { + mWebtoonImageView.setImage(ImageSource.uri(Uri.fromFile(mFile))); + } + + @Override + public void onImageConvertFailed() { + setError(itemView.getContext().getString(R.string.image_decode_error)); + } + + @Override + public void onPageDownloaded() { + mWebtoonImageView.setImage(ImageSource.uri(Uri.fromFile(mFile))); + } + + @Override + public void onPageDownloadFailed(Throwable reason) { + setError(ErrorUtils.getErrorMessageDetailed(getContext(), reason)); + } + + @Override + public void onPageDownloadProgress(int progress, int max) { + mTextProgressView.setProgress(progress, max); + } + + public void recycle() { + final MangaPage page = getData(); + super.recycle(); + if (page != null) { + PageDownloader.getInstance().cancel(page); + } + setError(null); + mWebtoonImageView.recycle(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonReaderAdapter.java b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonReaderAdapter.java new file mode 100644 index 00000000..2b1ad98a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonReaderAdapter.java @@ -0,0 +1,42 @@ +package org.nv95.openmanga.reader.webtoon; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.MangaPage; + +import java.util.ArrayList; + +final class WebtoonReaderAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + + WebtoonReaderAdapter(ArrayList dataset) { + mDataset = dataset; + } + + + @Override + public WebtoonPageHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new WebtoonPageHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_page_webtoon, parent, false)); + } + + @Override + public void onBindViewHolder(WebtoonPageHolder holder, int position) { + holder.bind(mDataset.get(position)); + } + + @Override + public void onViewRecycled(WebtoonPageHolder holder) { + holder.recycle(); + super.onViewRecycled(holder); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonReaderFragment.java b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonReaderFragment.java new file mode 100644 index 00000000..a91400f0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonReaderFragment.java @@ -0,0 +1,104 @@ +package org.nv95.openmanga.reader.webtoon; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.reader.ReaderFragment; + +public final class WebtoonReaderFragment extends ReaderFragment { + + private WebtoonRecyclerView mRecyclerView; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_reader_webtoon, container, false); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = view.findViewById(R.id.recyclerView); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setAdapter(new WebtoonReaderAdapter(mPages)); + mRecyclerView.setOnGestureListener(new TapDetector()); + } + + @Override + public int getCurrentPageIndex() { + return LayoutUtils.findFirstVisibleItemPosition(mRecyclerView); + } + + @Override + public void scrollToPage(int index) { + LayoutUtils.setSelectionFromTop(mRecyclerView, index); + } + + @Override + public void smoothScrollToPage(int index) { + mRecyclerView.smoothScrollToPosition(index); + } + + @Override + public boolean moveLeft() { + return false; + } + + @Override + public boolean moveRight() { + return false; + } + + @Override + public boolean moveUp() { + return false; + } + + @Override + public boolean moveDown() { + return false; + } + + @Override + public void onRestoreState(@NonNull Bundle savedState) { + super.onRestoreState(savedState); + } + + private final class TapDetector extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (mRecyclerView == null) { + return false; + } + final float x = e.getX(); + final float w3 = mRecyclerView.getWidth() / 3; + if (x < w3) { + return moveLeft(); + } else if (x <= w3 + w3) { + final float y = e.getY(); + final float h3 = mRecyclerView.getHeight() / 3; + if (y < h3) { + return moveLeft(); + } else if (y <= h3 + h3) { + toggleUi(); + return true; + } else { + return moveRight(); + } + } else { + return moveRight(); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonRecyclerView.java b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonRecyclerView.java new file mode 100644 index 00000000..688c85b6 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/reader/webtoon/WebtoonRecyclerView.java @@ -0,0 +1,47 @@ +package org.nv95.openmanga.reader.webtoon; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import org.nv95.openmanga.R; + +public final class WebtoonRecyclerView extends RecyclerView { + + @Nullable + private GestureDetector mGestureDetector = null; + + public WebtoonRecyclerView(Context context) { + this(context, null, 0); + } + + public WebtoonRecyclerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public WebtoonRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (mGestureDetector != null) { + mGestureDetector.onTouchEvent(ev); + } + return super.dispatchTouchEvent(ev); + } + + public void setOnGestureListener(@NonNull GestureDetector.SimpleOnGestureListener listener) { + mGestureDetector = new GestureDetector(getContext(), listener); + } + + @Nullable + private WebtoonImageView getWebtoonView(int pos) { + ViewHolder vh = findViewHolderForAdapterPosition(pos); + return vh != null ? vh.itemView.findViewById(R.id.webtoonImageView) : null; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsActivity.java b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsActivity.java new file mode 100644 index 00000000..e990ffdc --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsActivity.java @@ -0,0 +1,101 @@ +package org.nv95.openmanga.recommendations; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuItem; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class RecommendationsActivity extends AppBaseActivity { + + private ViewPager mPager; + private RecommendationsPagerAdapter mPagerAdapter; + private BroadcastReceiver mBroadcastReceiver; + @Nullable + private Snackbar mSnackBar; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_recommendations); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mPager = findViewById(R.id.pager); + TabLayout mTabs = findViewById(R.id.tabs); + + mPagerAdapter = new RecommendationsPagerAdapter(getFragmentManager(), this); + mPager.setAdapter(mPagerAdapter); + mTabs.setupWithViewPager(mPager); + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onUpdated(); + } + }; + final IntentFilter intentFilter = new IntentFilter(RecommendationsUpdateService.ACTION_RECOMMENDATIONS_UPDATED); + registerReceiver(mBroadcastReceiver, intentFilter); + if (mPagerAdapter.getCount() == 0) { + updateContent(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_recommendations, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_refresh: + updateContent(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void updateContent() { + if (mSnackBar != null) { + mSnackBar.dismiss(); + } + mSnackBar = Snackbar.make(mPager, R.string.updating_recommendations, Snackbar.LENGTH_INDEFINITE); + mSnackBar.addCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + mSnackBar = null; + super.onDismissed(transientBottomBar, event); + } + }); + mSnackBar.show(); + startService(new Intent(this, RecommendationsUpdateService.class)); + } + + @Override + protected void onDestroy() { + unregisterReceiver(mBroadcastReceiver); + super.onDestroy(); + } + + private void onUpdated() { + mPagerAdapter.notifyDataSetChanged(); + if (mSnackBar != null) { + mSnackBar.dismiss(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsAdapter.java b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsAdapter.java new file mode 100644 index 00000000..468e6f2f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsAdapter.java @@ -0,0 +1,95 @@ +package org.nv95.openmanga.recommendations; + +/* + * Created by koitharu on 29.01.18. + */ + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.core.models.MangaRecommendation; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.util.ArrayList; + +final class RecommendationsAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + + RecommendationsAdapter(ArrayList dataset) { + setHasStableIds(true); + mDataset = dataset; + } + + @Override + public RecommendationsHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new RecommendationsHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_manga_list, parent, false)); + } + + @Override + public void onBindViewHolder(RecommendationsHolder holder, int position) { + MangaRecommendation item = mDataset.get(position); + holder.text1.setText(item.name); + LayoutUtils.setTextOrHide(holder.text2, item.summary); + holder.summary.setText(item.genres); + ImageUtils.setThumbnail(holder.imageView, item.thumbnail, MangaProvider.getDomain(item.provider)); + holder.itemView.setTag(item); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).id; + } + + @Override + public void onViewRecycled(RecommendationsHolder holder) { + ImageUtils.recycle(holder.imageView); + super.onViewRecycled(holder); + } + + class RecommendationsHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final TextView text1; + final TextView text2; + final TextView summary; + final ImageView imageView; + + RecommendationsHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + text2 = itemView.findViewById(android.R.id.text2); + summary = itemView.findViewById(android.R.id.summary); + imageView = itemView.findViewById(R.id.imageView); + + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + final Context context = view.getContext(); + final MangaRecommendation item = mDataset.get(getAdapterPosition()); + switch (view.getId()) { + default: + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", item)); + } + } + } +} + diff --git a/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsFragment.java b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsFragment.java new file mode 100644 index 00000000..bcc7c698 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsFragment.java @@ -0,0 +1,100 @@ +package org.nv95.openmanga.recommendations; + +import android.app.Activity; +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.nv95.openmanga.AppBaseFragment; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaRecommendation; +import org.nv95.openmanga.core.storage.db.RecommendationsSpecifications; + +import java.util.ArrayList; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class RecommendationsFragment extends AppBaseFragment implements LoaderManager.LoaderCallbacks> { + + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + private RecommendationsSpecifications mSpecifications; + private RecommendationsAdapter mAdapter; + private ArrayList mDataset; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDataset = new ArrayList<>(); + mSpecifications = RecommendationsSpecifications.from(getArguments()); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_favourites, container,false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mProgressBar = view.findViewById(R.id.progressBar); + mRecyclerView = view.findViewById(R.id.recyclerView); + mTextViewHolder = view.findViewById(R.id.textView_holder); + + mTextViewHolder.setText(R.string.no_recommendations_tip); + mRecyclerView.setHasFixedSize(true); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Activity activity = getActivity(); + mAdapter = new RecommendationsAdapter(mDataset); + mRecyclerView.addItemDecoration(new DividerItemDecoration(activity, LinearLayoutManager.VERTICAL)); + mRecyclerView.setAdapter(mAdapter); + getLoaderManager().initLoader((int) mSpecifications.getId(), mSpecifications.toBundle(), this).forceLoad(); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new RecommendationsLoader(getActivity(), RecommendationsSpecifications.from(args)); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper result) { + mProgressBar.setVisibility(View.GONE); + if (result.isSuccess()) { + final ArrayList list = result.get(); + mDataset.clear(); + mDataset.addAll(list); + mAdapter.notifyDataSetChanged(); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + } else { + Snackbar.make(mRecyclerView, ErrorUtils.getErrorMessage(result.getError()), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } +} diff --git a/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsLoader.java b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsLoader.java new file mode 100644 index 00000000..db28aff3 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsLoader.java @@ -0,0 +1,37 @@ +package org.nv95.openmanga.recommendations; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaRecommendation; +import org.nv95.openmanga.core.storage.db.RecommendationsRepository; +import org.nv95.openmanga.core.storage.db.RecommendationsSpecifications; + +import java.util.ArrayList; + +/** + * Created by koitharu on 29.01.18. + */ + +final class RecommendationsLoader extends AsyncTaskLoader> { + + private final RecommendationsSpecifications mSpecifications; + + public RecommendationsLoader(Context context, RecommendationsSpecifications specifications) { + super(context); + mSpecifications = specifications; + } + + @Override + public ListWrapper loadInBackground() { + try { + final RecommendationsRepository repository = RecommendationsRepository.get(getContext()); + final ArrayList list = repository.query(mSpecifications); + return list == null ? ListWrapper.badList() : new ListWrapper<>(list); + } catch (Exception e) { + e.printStackTrace(); + return new ListWrapper<>(e); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsPagerAdapter.java b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsPagerAdapter.java new file mode 100644 index 00000000..d7b9a9b0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsPagerAdapter.java @@ -0,0 +1,61 @@ +package org.nv95.openmanga.recommendations; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Context; +import android.content.res.Resources; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.NativeFragmentPagerAdapter; +import org.nv95.openmanga.core.storage.db.RecommendationsRepository; +import org.nv95.openmanga.core.storage.db.RecommendationsSpecifications; + +import java.util.ArrayList; + +/** + * Created by koitharu on 29.01.18. + */ + +class RecommendationsPagerAdapter extends NativeFragmentPagerAdapter { + + private final RecommendationsRepository mRepository; + private final ArrayList mDataset; + private final Resources mResources; + + RecommendationsPagerAdapter(FragmentManager fm, Context context) { + super(fm); + mRepository = RecommendationsRepository.get(context); + mResources = context.getResources(); + mDataset = mRepository.getCategories(); + } + + @NonNull + @Override + public Fragment getItem(int position) { + final RecommendationsFragment fragment = new RecommendationsFragment(); + fragment.setArguments(new RecommendationsSpecifications() + .category(mDataset.get(position)) + //.orderByRand() + .toBundle()); + return fragment; + } + + @Override + public void notifyDataSetChanged() { + mDataset.clear(); + mDataset.addAll(mRepository.getCategories()); + super.notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mDataset.size(); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return mResources.getString(mDataset.get(position)); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsUpdateService.java b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsUpdateService.java new file mode 100644 index 00000000..3f0dacff --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/recommendations/RecommendationsUpdateService.java @@ -0,0 +1,97 @@ +package org.nv95.openmanga.recommendations; + +import android.app.IntentService; +import android.content.Intent; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaRecommendation; +import org.nv95.openmanga.core.models.ProviderHeader; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.storage.ProvidersStore; +import org.nv95.openmanga.core.storage.db.RecommendationsRepository; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Random; + +/** + * Created by koitharu on 29.01.18. + */ + +public final class RecommendationsUpdateService extends IntentService { + + public static final String ACTION_RECOMMENDATIONS_UPDATED = "org.nv95.openmanga.ACTION_RECOMMENDATIONS_UPDATED"; + + public RecommendationsUpdateService() { + super("recommendations"); + setIntentRedelivery(true); + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + final RecommendationsRepository repository = RecommendationsRepository.get(this); + final ArrayList providers = new ProvidersStore(this).getUserProviders(); + final Random random = new Random(); + repository.clear(); + for (ProviderHeader o : providers) { + final MangaProvider provider = MangaProvider.get(this, o.cName); + //popular + int sort = MangaProvider.findSortIndex(provider, R.string.sort_popular); + if (sort == -1) { + sort = MangaProvider.findSortIndex(provider, R.string.sort_rating); + } + if (sort != -1) { + try { + ArrayList list = provider.query(null, random.nextInt(4), sort, new String[0]); + Collections.shuffle(list); + int count = Math.min(10, list.size()); + for (int i = 0; i < count; i++) { + repository.add(new MangaRecommendation( + list.get(i), + R.string.sort_popular + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + //newest + sort = MangaProvider.findSortIndex(provider, R.string.sort_latest); + if (sort != -1) { + try { + ArrayList list = provider.query(null, random.nextInt(4), sort, new String[0]); + Collections.shuffle(list); + int count = Math.min(10, list.size()); + for (int i = 0; i < count; i++) { + repository.add(new MangaRecommendation( + list.get(i), + R.string.sort_latest + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + //newest + sort = MangaProvider.findSortIndex(provider, R.string.sort_updated); + if (sort != -1) { + try { + ArrayList list = provider.query(null, random.nextInt(4), sort, new String[0]); + Collections.shuffle(list); + int count = Math.min(10, list.size()); + for (int i = 0; i < count; i++) { + repository.add(new MangaRecommendation( + list.get(i), + R.string.sort_updated + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + sendBroadcast(new Intent(ACTION_RECOMMENDATIONS_UPDATED)); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/search/SearchActivity.java b/app/src/main/java/org/nv95/openmanga/search/SearchActivity.java new file mode 100644 index 00000000..c9781901 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/search/SearchActivity.java @@ -0,0 +1,144 @@ +package org.nv95.openmanga.search; + +import android.app.LoaderManager; +import android.app.SearchManager; +import android.content.Intent; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.views.EndlessRecyclerView; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.ProviderHeader; +import org.nv95.openmanga.core.storage.ProvidersStore; + +import java.util.ArrayList; +import java.util.Stack; + +/** + * Created by koitharu on 06.01.18. + */ + +public final class SearchActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks>, + EndlessRecyclerView.OnLoadMoreListener, View.OnClickListener { + + private EndlessRecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + private final Stack mProviders = new Stack<>(); + private SearchQueryArguments mQueryArguments; + + private final ArrayList mDataset = new ArrayList<>(); + private SearchAdapter mAdapter; + + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_search); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mRecyclerView = findViewById(R.id.recyclerView); + mProgressBar = findViewById(R.id.progressBar); + mTextViewHolder = findViewById(R.id.textView_holder); + + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.setOnLoadMoreListener(this); + + Intent intent = getIntent(); + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + String query = intent.getStringExtra(SearchManager.QUERY); + beginSearch(query); + } + } + + private void beginSearch(@NonNull String query) { + mProgressBar.setVisibility(View.VISIBLE); + mTextViewHolder.setVisibility(View.GONE); + setSubtitle(query); + SearchSuggestionsProvider.getSuggestions(this).saveRecentQuery(query, null); + mProviders.clear(); + mProviders.addAll(new ProvidersStore(this).getUserProviders()); + mQueryArguments = new SearchQueryArguments(query, mProviders.pop().cName); + mRecyclerView.onLoadingStarted(); + mDataset.clear(); + mAdapter = new SearchAdapter(mDataset); + mRecyclerView.setAdapter(mAdapter); + getLoaderManager().initLoader(0, mQueryArguments.toBundle(), this).forceLoad(); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new SearchLoader(this, SearchQueryArguments.from(args)); + } + + @Override + public void onLoadFinished(Loader> loader, @NonNull ListWrapper result) { + /*if (result.isFailed()) { + Snackbar.make(mRecyclerView, R.string.loading_error, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.retry, this) + .show(); + mProgressBar.setVisibility(View.GONE); + if (mDataset.isEmpty()) { + mTextViewHolder.setVisibility(View.VISIBLE); + } + mRecyclerView.onLoadingFinished(true); + } else */ + if (result.isFailed() || result.isEmpty()) { + //next provider + if (mProviders.empty()) { + mProgressBar.setVisibility(View.GONE); + mRecyclerView.onLoadingFinished(false); + if (mDataset.isEmpty()) { + mTextViewHolder.setVisibility(View.VISIBLE); + } + } else { + mQueryArguments.page = 0; + mQueryArguments.providerCName = mProviders.pop().cName; + mRecyclerView.onLoadingFinished(true); + } + } else { + mProgressBar.setVisibility(View.GONE); + int firstPos = mDataset.size(); + mDataset.addAll(result.get()); + if (firstPos == 0) { + mAdapter.notifyDataSetChanged(); + } else { + mAdapter.notifyItemRangeInserted(firstPos, result.size()); + } + mQueryArguments.page++; + mRecyclerView.onLoadingFinished(true); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public boolean onLoadMore() { + mRecyclerView.onLoadingStarted(); + getLoaderManager().restartLoader(0, mQueryArguments.toBundle(), this).forceLoad(); + return true; + } + + @Override + public void onClick(View v) { + if (mDataset.isEmpty()) { + mProgressBar.setVisibility(View.VISIBLE); + } + mTextViewHolder.setVisibility(View.GONE); + onLoadMore(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/search/SearchAdapter.java b/app/src/main/java/org/nv95/openmanga/search/SearchAdapter.java new file mode 100644 index 00000000..8ba7ece4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/search/SearchAdapter.java @@ -0,0 +1,161 @@ +package org.nv95.openmanga.search; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.IntDef; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.LayoutUtils; +import org.nv95.openmanga.common.views.EndlessRecyclerView; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Created by koitharu on 07.01.18. + */ + +public final class SearchAdapter extends RecyclerView.Adapter implements + EndlessRecyclerView.EndlessAdapter, View.OnClickListener { + + private final ArrayList mDataset; + private boolean mHasNext = true; + + public SearchAdapter(ArrayList dataset) { + mDataset = dataset; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, @ItemViewType int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + RecyclerView.ViewHolder holder; + switch (viewType) { + case ItemViewType.TYPE_ITEM_MANGA: + holder = new MangaHeaderHolder(inflater.inflate(R.layout.item_manga_list, parent, false)); + break; + case ItemViewType.TYPE_ITEM_PROGRESS: + return new ProgressHolder(inflater.inflate(R.layout.item_progress, parent, false)); + default: + throw new AssertionError("Unknown viewType"); + case ItemViewType.TYPE_ITEM_SUBHEADER: + return new HeaderHolder(inflater.inflate(R.layout.header_group, parent, false)); + } + holder.itemView.setOnClickListener(this); + return holder; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof MangaHeaderHolder) { + final MangaHeader item = (MangaHeader) mDataset.get(position); + ((MangaHeaderHolder) holder).text1.setText(item.name); + LayoutUtils.setTextOrHide(((MangaHeaderHolder) holder).text2, item.summary); + ((MangaHeaderHolder) holder).summary.setText(item.genres); + ImageUtils.setThumbnail(((MangaHeaderHolder) holder).imageView, item.thumbnail, MangaProvider.getDomain(item.provider)); + holder.itemView.setTag(item); + } else if (holder instanceof ProgressHolder) { + ((ProgressHolder) holder).progressBar.setVisibility(mHasNext ? View.VISIBLE : View.INVISIBLE); + } else if (holder instanceof HeaderHolder) { + String item = (String) mDataset.get(position); + ((HeaderHolder) holder).textView.setText(item); + } + } + + @Override + public void setHasNext(boolean hasNext) { + mHasNext = hasNext; + if (mDataset.size() != 0) { + notifyItemChanged(mDataset.size()); + } + } + + @Override + public int getItemCount() { + final int size = mDataset.size(); + return size == 0 ? 0 : size + 1; + } + + @ItemViewType + @Override + public int getItemViewType(int position) { + if (position == mDataset.size()) { + return ItemViewType.TYPE_ITEM_PROGRESS; + } else { + return mDataset.get(position) instanceof MangaHeader ? ItemViewType.TYPE_ITEM_MANGA : ItemViewType.TYPE_ITEM_SUBHEADER; + } + } + + @Override + public long getItemId(int position) { + if (position == mDataset.size()) { + return 0; + } else { + final Object item = mDataset.get(position); + return item instanceof MangaHeader ? ((MangaHeader) item).id : item.hashCode(); + } + } + + @Override + public void onClick(View view) { + MangaHeader mangaHeader = (MangaHeader) view.getTag(); + Context context = view.getContext(); + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", mangaHeader)); + } + + final class MangaHeaderHolder extends RecyclerView.ViewHolder { + + final TextView text1; + final TextView text2; + final TextView summary; + final ImageView imageView; + + MangaHeaderHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + text2 = itemView.findViewById(android.R.id.text2); + summary = itemView.findViewById(android.R.id.summary); + imageView = itemView.findViewById(R.id.imageView); + } + } + + final class ProgressHolder extends RecyclerView.ViewHolder { + + final ProgressBar progressBar; + + ProgressHolder(View itemView) { + super(itemView); + progressBar = itemView.findViewById(android.R.id.progress); + } + } + + final class HeaderHolder extends RecyclerView.ViewHolder { + + final TextView textView; + + HeaderHolder(View itemView) { + super(itemView); + textView = itemView.findViewById(R.id.textView); + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ItemViewType.TYPE_ITEM_MANGA, ItemViewType.TYPE_ITEM_PROGRESS, ItemViewType.TYPE_ITEM_SUBHEADER}) + public @interface ItemViewType { + int TYPE_ITEM_MANGA = 0; + int TYPE_ITEM_PROGRESS = 1; + int TYPE_ITEM_SUBHEADER = 2; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/search/SearchLoader.java b/app/src/main/java/org/nv95/openmanga/search/SearchLoader.java new file mode 100644 index 00000000..c95e7e0b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/search/SearchLoader.java @@ -0,0 +1,33 @@ +package org.nv95.openmanga.search; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.providers.MangaProvider; + +/** + * Created by koitharu on 07.01.18. + */ + +public final class SearchLoader extends AsyncTaskLoader> { + + private final SearchQueryArguments mArguments; + + public SearchLoader(Context context, SearchQueryArguments arguments) { + super(context); + mArguments = arguments; + } + + @Override + public ListWrapper loadInBackground() { + try { + MangaProvider provider = MangaProvider.get(getContext(), mArguments.providerCName); + return new ListWrapper<>(provider.query(mArguments.query, mArguments.page, -1, new String[0])); + } catch (Exception e) { + e.printStackTrace(); + return new ListWrapper<>(e); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/search/SearchQueryArguments.java b/app/src/main/java/org/nv95/openmanga/search/SearchQueryArguments.java new file mode 100644 index 00000000..8eb6dbaa --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/search/SearchQueryArguments.java @@ -0,0 +1,41 @@ +package org.nv95.openmanga.search; + +import android.os.Bundle; +import android.support.annotation.NonNull; + +/** + * Created by koitharu on 07.01.18. + */ + +public final class SearchQueryArguments { + + @NonNull + public String query; + @NonNull + public String providerCName; + public int page; + + public SearchQueryArguments(@NonNull String query, @NonNull String providerCName) { + this.query = query; + this.providerCName = providerCName; + this.page = 0; + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(3); + bundle.putString("query", query); + bundle.putString("cname", providerCName); + bundle.putInt("page", page); + return bundle; + } + + public static SearchQueryArguments from(@NonNull Bundle bundle) { + final SearchQueryArguments result = new SearchQueryArguments( + bundle.getString("query", ""), + bundle.getString("cname", "") + ); + result.page = bundle.getInt("page", 0); + return result; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/search/SearchSuggestionsProvider.java b/app/src/main/java/org/nv95/openmanga/search/SearchSuggestionsProvider.java new file mode 100644 index 00000000..10a06d12 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/search/SearchSuggestionsProvider.java @@ -0,0 +1,26 @@ +package org.nv95.openmanga.search; + +import android.content.Context; +import android.content.SearchRecentSuggestionsProvider; +import android.provider.SearchRecentSuggestions; +import android.support.annotation.NonNull; + +/** + * Created by koitharu on 07.01.18. + */ + +public final class SearchSuggestionsProvider extends SearchRecentSuggestionsProvider { + + final static String AUTHORITY = "org.nv95.openmanga.SEARCH_SUGGEST"; + final static int MODE = DATABASE_MODE_QUERIES; + + public SearchSuggestionsProvider() { + setupSuggestions(AUTHORITY, MODE); + } + + @NonNull + public static SearchRecentSuggestions getSuggestions(Context context) { + return new SearchRecentSuggestions(context, SearchSuggestionsProvider.AUTHORITY, SearchSuggestionsProvider.MODE); + } + +} diff --git a/app/src/main/java/org/nv95/openmanga/services/ExportService.java b/app/src/main/java/org/nv95/openmanga/services/ExportService.java deleted file mode 100644 index 8db893dc..00000000 --- a/app/src/main/java/org/nv95/openmanga/services/ExportService.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.nv95.openmanga.services; - -import android.annotation.SuppressLint; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Environment; -import android.os.IBinder; -import android.os.PowerManager; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.NotificationHelper; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.utils.StorageUtils; -import org.nv95.openmanga.utils.ZipBuilder; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Locale; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; - -/** - * Created by admin on 20.07.17. - */ - -public class ExportService extends Service { - - private final ThreadPoolExecutor mExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2); - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - MangaInfo mangaInfo = new MangaInfo(intent.getExtras()); - new ExportTask(mangaInfo).executeOnExecutor(mExecutor); - return START_NOT_STICKY; - } - - @SuppressLint("StaticFieldLeak") - private class ExportTask extends AsyncTask { - - private final MangaInfo mManga; - private final int mNotificationId; - private final NotificationHelper mNotificationHelper; - private final PowerManager.WakeLock mWakeLock; - - ExportTask(MangaInfo manga) { - mManga = manga; - mNotificationId = manga.id; - mNotificationHelper = new NotificationHelper(ExportService.this); - mWakeLock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Export manga"); - mNotificationHelper.group(ExportTask.class.getName()); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mWakeLock.acquire(30*60*1000L /*30 minutes*/); - mNotificationHelper - .title(R.string.exporting) - .text(mManga.name) - .indeterminate() - .ongoing() - .icon(android.R.drawable.stat_sys_upload) - .image(mManga.preview) - .foreground(mNotificationId); - } - - @Override - protected String doInBackground(Void... voids) { - ZipBuilder zipBuilder = null; - try { - LocalMangaProvider provider = LocalMangaProvider.getInstance(ExportService.this); - MangaSummary summary = provider.getDetailedInfo(mManga); - ArrayList pages = new ArrayList<>(); - for (MangaChapter chapter: summary.chapters) { - pages.addAll(provider.getPages(chapter.readLink)); - } - final File destDir = new File(Environment.getExternalStorageDirectory(), "Manga"); - if (!destDir.exists() && !destDir.mkdir()) { - return null; - } - zipBuilder = new ZipBuilder( - StorageUtils.uniqueFile(destDir, StorageUtils.escapeFilename(mManga.name) + ".cbz") - ); - final int total = pages.size(); - for (int i=0;i mTaskReference = new WeakReference<>(null); - - @Override - public void onCreate() { - super.onCreate(); - mNotificationHelper = new NotificationHelper(this) - .text(R.string.preparing) - .indeterminate() - .actionCancel(PendingIntent.getService( - this, 0, - new Intent(this, ImportService.class).putExtra("action", ACTION_CANCEL), - 0)); - startForeground(NOTIFY_ID, mNotificationHelper.notification()); - mWakeLock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Saving manga"); - mWakeLock.acquire(); - } - - @Override - public void onDestroy() { - mWakeLock.release(); - stopForeground(false); - super.onDestroy(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - int action = intent != null ? intent.getIntExtra("action", 0) : 0; - ImportTask task = mTaskReference.get(); - switch (action) { - case ACTION_START: - if (task != null) { - Toast.makeText(this, R.string.wait_for_complete, Toast.LENGTH_SHORT).show(); - break; - } - String path = intent.getStringExtra(Intent.EXTRA_TEXT); - task = new ImportTask(new File(path).getName()); - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, path); - mTaskReference = new WeakReference<>(task); - break; - case ACTION_CANCEL: - if (task != null) { - mNotificationHelper - .noActions() - .indeterminate() - .text(R.string.cancelling) - .update(NOTIFY_ID); - task.cancel(false); - } else { - stopSelf(); - } - break; - } - return super.onStartCommand(intent, flags, startId); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - private class ImportTask extends AsyncTask { - private final String mName; - - public ImportTask(String name) { - this.mName = name; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mNotificationHelper - .indeterminate() - .icon(android.R.drawable.stat_sys_download) - .title(getString(R.string.import_file) + " " + mName) - .text(R.string.preparing) - .update(NOTIFY_ID, R.string.import_file); - } - - @Override - protected Integer doInBackground(String... params) { - SQLiteDatabase database; - ZipInputStream zipInputStream = null; - int pages = 0; - String info = getString(R.string.imported_from, new File(params[0]).getName()); - try { - //reading info - final ZipEntry[] entries = ZipBuilder.enumerateEntries(params[0]); - String name = entries[0].getName(); - name = name.substring(0, name.length() - 1); - int total = 0; - for (ZipEntry o:entries) { - if (!o.isDirectory()) { - total++; - } - } - int mangaId; - int chapterId; - int pageId; - File preview = null; - zipInputStream = new ZipInputStream(new FileInputStream(params[0])); - final File dest = new File(MangaStore.getMangasDir(ImportService.this), - String.valueOf(mangaId = params[0].hashCode())); - if (dest.exists()) { - new DirRemoveHelper(dest).run(); - } - dest.mkdirs(); - final byte[] buffer = new byte[1024]; - publishProgress(0, entries.length); - //importing - MangaStore ms = new MangaStore(ImportService.this); - ContentValues cv; - //all pages - chapterId = mangaId; - File outFile; - ZipEntry entry; - FileOutputStream outputStream; - while ((entry = zipInputStream.getNextEntry()) != null && !isCancelled()) { - if (!entry.isDirectory()) { - if (StorageUtils.isImageFile(entry.getName())) { - pageId = entry.getName().hashCode(); - outFile = new File(dest, chapterId + "_" + pageId); - if (outFile.exists() || outFile.createNewFile()) { - outputStream = new FileOutputStream(outFile); - int len; - while ((len = zipInputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, len); - } - outputStream.close(); - cv = new ContentValues(); - cv.put("id", pageId); - cv.put("chapterid", chapterId); - cv.put("mangaid", mangaId); - cv.put("file", outFile.getName()); - cv.put("number", pages); - ms.getDatabase(true).insert(TABLE_PAGES, null, cv); - pages++; - publishProgress(pages, total); - if (preview == null) { - preview = new File(dest, "cover"); - StorageUtils.copyFile(outFile, preview); - } - } - } else if (entry.getName().toLowerCase().endsWith("info.txt")) { - info = StorageUtils.tail(zipInputStream, 20) + "\n" + info; - } - } - } - if (isCancelled()) { - //remove all - ms.getDatabase(true).delete(TABLE_PAGES, "mangaid=?", new String[]{String.valueOf(mangaId)}); - new DirRemoveHelper(dest).run(); - try { - zipInputStream.close(); - } catch (IOException ignored) { - } - return null; - } - //save chapter - cv = new ContentValues(); - cv.put("id", chapterId); - cv.put("mangaid", mangaId); - cv.put("name", "default"); - cv.put("number", 0); - if (ms.getDatabase(true).update(TABLE_CHAPTERS, cv, "id=? AND mangaid=?", new String[]{String.valueOf(chapterId), String.valueOf(mangaId)}) == 0) { - ms.getDatabase(true).insertOrThrow(TABLE_CHAPTERS, null, cv); - } - //save manga - cv = new ContentValues(); - cv.put("id", mangaId); - cv.put("name", name); - cv.put("subtitle", ""); - cv.put("summary", ""); - cv.put("description", info); - cv.put("dir", MangaStore.getMangaDir(ImportService.this, ms.getDatabase(true), mangaId).getPath()); - cv.put("timestamp", new Date().getTime()); - if (ms.getDatabase(true).update(TABLE_MANGAS, cv, "id=?", new String[]{String.valueOf(mangaId)}) == 0) { - ms.getDatabase(true).insertOrThrow(TABLE_MANGAS, null, cv); - } - } catch (Exception e) { - FileLogger.getInstance().report(e); - pages = -1; - } finally { - if (zipInputStream != null) { - try { - zipInputStream.close(); - } catch (IOException ignored) { - } - } - } - return pages; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - mNotificationHelper - .progress(values[0], values[1]) - .text(getString(R.string.import_progress, values[0], values[1])) - .update(NOTIFY_ID); - } - - @Override - protected void onPostExecute(Integer integer) { - super.onPostExecute(integer); - stopSelf(); - mNotificationHelper - .noActions() - .noProgress() - .autoCancel() - .icon(integer == -1 ? android.R.drawable.stat_notify_error : android.R.drawable.stat_sys_download_done) - .text(integer == -1 ? R.string.error : R.string.import_complete) - .update(NOTIFY_ID); - mTaskReference = new WeakReference<>(null); - ChangesObserver.getInstance().emitOnLocalChanged(-1, null); - } - - @Override - protected void onCancelled() { - super.onCancelled(); - stopSelf(); - mTaskReference = new WeakReference<>(null); - mNotificationHelper.dismiss(NOTIFY_ID); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/services/SaveService.java b/app/src/main/java/org/nv95/openmanga/services/SaveService.java deleted file mode 100644 index ad9308ec..00000000 --- a/app/src/main/java/org/nv95/openmanga/services/SaveService.java +++ /dev/null @@ -1,602 +0,0 @@ -package org.nv95.openmanga.services; - -import android.annotation.SuppressLint; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.net.ConnectivityManager; -import android.os.AsyncTask; -import android.os.Binder; -import android.os.IBinder; -import android.os.PowerManager; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.DownloadsActivity; -import org.nv95.openmanga.activities.PreviewActivity2; -import org.nv95.openmanga.helpers.NotificationHelper; -import org.nv95.openmanga.helpers.SpeedMeasureHelper; -import org.nv95.openmanga.items.DownloadInfo; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.MangaStore; -import org.nv95.openmanga.utils.NetworkStateListener; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.PausableAsyncTask; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Set; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; - -/** - * Created by admin on 21.07.17. - */ - -public class SaveService extends Service implements NetworkStateListener.OnNetworkStateListener, SharedPreferences.OnSharedPreferenceChangeListener { - - public static final int ACTION_ADD = 50; - public static final int ACTION_CANCEL = 51; - public static final int ACTION_PAUSE = 52; - public static final int ACTION_RESUME = 53; - public static final int ACTION_CANCEL_ALL = 54; - - private ThreadPoolExecutor mExecutor; - private final LinkedHashMap mTasks = new LinkedHashMap<>(); - private final ArrayList mProgressListeners = new ArrayList<>(); - private int mForegroundId = 0; - private final NetworkStateListener mNetworkListener = new NetworkStateListener(this); - - @Override - public void onCreate() { - super.onCreate(); - mExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool( - PreferenceManager.getDefaultSharedPreferences(this).getInt("save_threads", 2) - ); - registerReceiver(mNetworkListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onDestroy() { - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(this); - unregisterReceiver(mNetworkListener); - super.onDestroy(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - int action = intent != null ? intent.getIntExtra("action", 0) : 0; - int id = intent != null ? intent.getIntExtra("task_id", 0) : 0; - switch (action) { - case ACTION_ADD: - MangaSummary mangaSummary = new MangaSummary(intent.getExtras()); - final DownloadInfo download = new DownloadInfo(mangaSummary); - SaveTask saveTask = new SaveTask(download); - if (canDownloadNow()) { - Toast.makeText(this, R.string.download_started, Toast.LENGTH_SHORT).show(); - } else { - saveTask.setPaused(true); - Toast.makeText(this, R.string.download_starts_on_network, Toast.LENGTH_SHORT).show(); - } - mTasks.put(download.id, saveTask); - saveTask.executeOnExecutor(mExecutor); - break; - case ACTION_CANCEL: - if (id == 0) break; - saveTask = mTasks.get(id); - if (saveTask != null && saveTask.canCancel()) { - saveTask.onCancel(); - saveTask.cancel(true); - } - mTasks.remove(id); - for (OnSaveProgressListener o : mProgressListeners) { - o.onDataUpdated(); - } - break; - case ACTION_PAUSE: - if (id == 0) break; - saveTask = mTasks.get(id); - if (saveTask != null) { - saveTask.pause(); - } - break; - case ACTION_RESUME: - if (id == 0) break; - saveTask = mTasks.get(id); - if (saveTask != null) { - saveTask.resume(); - } - break; - case ACTION_CANCEL_ALL: - for (SaveTask o : mTasks.values()) { - if (o.canCancel()) { - o.onCancel(); - o.cancel(true); - } - } - mTasks.clear(); - mExecutor.shutdown(); - for (OnSaveProgressListener o:mProgressListeners) { - o.onDataUpdated(); - } - break; - } - return START_REDELIVER_INTENT; - } - - @Override - public void onNetworkStatusChanged(boolean isConnected) { - if (!isConnected) { - for (SaveTask o : mTasks.values()) { - o.pause(); - } - } else if (canDownloadNow()) { - for (SaveTask o : mTasks.values()) { - o.resume(); - } - } - } - - private boolean canDownloadNow() { - boolean isWifiOnly = PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean("save.wifionly", false); - return NetworkUtils.checkConnection(this, isWifiOnly); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - switch (key) { - case "save.wifionly": - boolean paused = !canDownloadNow(); - for (SaveTask o : mTasks.values()) { - o.setPaused(paused); - } - break; - } - } - - @SuppressLint("StaticFieldLeak") - private class SaveTask extends PausableAsyncTask { - - //прогресс по главам - static final int PROGRESS_PRIMARY = 0; - //прогресс по страницам - static final int PROGRESS_SECONDARY = 1; - //в случае ошибки - static final int PROGRESS_ERROR = 2; - //first call - static final int PROGRESS_STARTED = 3; - private final DownloadInfo mDownload; - private final NotificationHelper mNotificationHelper; - private final PowerManager.WakeLock mWakeLock; - boolean isStarted = false; - - SaveTask(DownloadInfo downloadInfo) { - mDownload = downloadInfo; - mNotificationHelper = new NotificationHelper(SaveService.this); - mWakeLock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Save manga"); - mNotificationHelper.group(SaveTask.class.getName()); - } - - @Override - protected void onPreExecute() { - for (OnSaveProgressListener o:mProgressListeners) { - o.onDataUpdated(); - } - } - - private void onRealStarted() { - if (!mWakeLock.isHeld()) { - mWakeLock.acquire(30 * 60 * 1000L /*30 minutes*/); - } - isStarted = true; - mNotificationHelper - .title(mDownload.name) - .text(R.string.saving_manga) - .indeterminate() - .ongoing() - .info(null) - .intentActivity(new Intent(SaveService.this, DownloadsActivity.class), mDownload.id + 11) - .icon(android.R.drawable.stat_sys_download) - .image(mDownload.preview) - .actionCancel(PendingIntent.getService( - SaveService.this, ACTION_CANCEL + mDownload.id, - new Intent(SaveService.this, SaveService.class) - .putExtra("task_id", mDownload.id) - .putExtra("action", ACTION_CANCEL), - PendingIntent.FLAG_UPDATE_CURRENT)) - .actionSecondary(PendingIntent.getService( - SaveService.this, ACTION_PAUSE + mDownload.id, - new Intent(SaveService.this, SaveService.class) - .putExtra("task_id", mDownload.id) - .putExtra("action", ACTION_PAUSE), - PendingIntent.FLAG_UPDATE_CURRENT), R.drawable.sym_pause, R.string.pause); - if (mForegroundId == 0) { - mNotificationHelper.foreground(mDownload.id); - mForegroundId = mDownload.id; - } - for (OnSaveProgressListener o:mProgressListeners) { - o.onDataUpdated(mDownload.id); - } - } - - @Override - public void onPaused() { - if (mWakeLock.isHeld()) { - mWakeLock.release(); - } - mNotificationHelper - .actionSecondary(PendingIntent.getService( - SaveService.this, ACTION_RESUME + mDownload.id, - new Intent(SaveService.this, SaveService.class) - .putExtra("task_id", mDownload.id) - .putExtra("action", ACTION_RESUME), - PendingIntent.FLAG_UPDATE_CURRENT), - R.drawable.sym_resume, - R.string.resume) - .icon(R.drawable.ic_stat_paused) - .title(mDownload.name) - .info(null) - .update(mDownload.id); - for (OnSaveProgressListener o:mProgressListeners) { - o.onDataUpdated(mDownload.id); - } - } - - @Override - public void onResumed() { - if (!mWakeLock.isHeld()) { - mWakeLock.acquire(30 * 60 * 1000L /*30 minutes*/); - } - mNotificationHelper - .actionSecondary(PendingIntent.getService( - SaveService.this, ACTION_PAUSE + mDownload.id, - new Intent(SaveService.this, SaveService.class) - .putExtra("task_id", mDownload.id) - .putExtra("action", ACTION_PAUSE), - PendingIntent.FLAG_UPDATE_CURRENT), - R.drawable.sym_pause, - R.string.pause) - .icon(android.R.drawable.stat_sys_download) - .title(mDownload.name) - .info(null) - .update(mDownload.id); - for (OnSaveProgressListener o:mProgressListeners) { - o.onDataUpdated(mDownload.id); - } - } - - @Override - protected MangaInfo doInBackground(Void... voids) { - publishProgress(PROGRESS_STARTED); - final SpeedMeasureHelper speedMeasureHelper = new SpeedMeasureHelper(); - try { - MangaProvider provider = MangaProviderManager.instanceProvider(SaveService.this, mDownload.provider); - final MangaStore store = new MangaStore(SaveService.this); - final int mangaId = store.pushManga(mDownload); - //собственно, скачивание - ArrayList pages; - MangaChapter o; - MangaPage o1; - //all chapters - for (int i=0; i= 4) { - double kbps = values[3] / 1024D; - mNotificationHelper.info(SpeedMeasureHelper.formatSpeed(kbps)); - } - mNotificationHelper - .progress(mDownload.pos * 100 + mDownload.getChapterProgressPercent(), mDownload.max * 100) - .update(mDownload.id); - if (isCancelled()) { - for (OnSaveProgressListener o : mProgressListeners) { - o.onProgressUpdated(mDownload.id); - } - } else { - for (OnSaveProgressListener o : mProgressListeners) { - o.onDataUpdated(mDownload.id); - } - } - } - - void onCancel() { - mNotificationHelper - .noActions() - .info(null) - .indeterminate() - .text(R.string.cancelling) - .update(mDownload.id); - } - - @Override - protected void onCancelled() { - super.onCancelled(); - if (mWakeLock.isHeld()) { - mWakeLock.release(); - } - if (mForegroundId == mDownload.id) { - switchForeground(mNotificationHelper); - } - mNotificationHelper.dismiss(mDownload.id); - for (OnSaveProgressListener o:mProgressListeners) { - o.onDataUpdated(); - } - if (mExecutor.getTaskCount() == mExecutor.getCompletedTaskCount()) { - stopSelf(); - } - } - - @WorkerThread - boolean onError() { - pause(); - if (canDownloadNow()) { - publishProgress(PROGRESS_ERROR); - } - return waitForResume(); - } - - @Override - protected void onPostExecute(MangaInfo manga) { - super.onPostExecute(manga); - if (mWakeLock.isHeld()) { - mWakeLock.release(); - } - if (mForegroundId == mDownload.id) { - switchForeground(mNotificationHelper); - } - mNotificationHelper - .noActions() - .cancelable() - .noProgress(); - if (manga != null) { - mNotificationHelper - .title(manga.name) - .expandable(manga.name) - .icon(android.R.drawable.stat_sys_download_done) - .intentActivity(new Intent(SaveService.this, PreviewActivity2.class) - .putExtras(manga.toBundle()), mDownload.id + 11) - .autoCancel() - .text(R.string.done); - } else { - mNotificationHelper - .icon(R.drawable.ic_stat_error) - .text(R.string.error); - } - mNotificationHelper.update(mDownload.id); - for (OnSaveProgressListener o:mProgressListeners) { - o.onDataUpdated(mDownload.id); - } - if (mExecutor.getTaskCount() == mExecutor.getCompletedTaskCount()) { - stopSelf(); - } - } - } - - private void switchForeground(NotificationHelper current) { - for (SaveTask o : mTasks.values()) { - if (o.getStatus() == AsyncTask.Status.RUNNING && o.mDownload.id != mForegroundId) { - mForegroundId = o.mDownload.id; - o.mNotificationHelper.foreground(mForegroundId); - return; - } - } - current.stopForeground(); - mForegroundId = 0; - } - - public interface OnSaveProgressListener { - void onProgressUpdated(int id); - void onDataUpdated(int id); - void onDataUpdated(); - } - - public static class SaveServiceBinder extends Binder { - - private final SaveService mService; - - SaveServiceBinder(SaveService service) { - mService = service; - } - - public int getTaskCount() { - return mService.mTasks.size(); - } - - public DownloadInfo getItemById(int id) { - return mService.mTasks.get(id).mDownload; - } - - public void addListener(OnSaveProgressListener listener) { - mService.mProgressListeners.add(listener); - } - - public void removeListener(OnSaveProgressListener listener) { - mService.mProgressListeners.remove(listener); - } - - public void pauseAll() { - for (SaveTask o : mService.mTasks.values()) { - o.pause(); - } - } - - public void resumeAll() { - for (SaveTask o : mService.mTasks.values()) { - o.resume(); - } - } - - public boolean isPaused(int id) { - return mService.mTasks.get(id).isPaused(); - } - - public void setPaused(int id, boolean value) { - mService.mTasks.get(id).setPaused(value); - } - - public void cancelAndRemove(int id) { - SaveTask task = mService.mTasks.get(id); - if (task.canCancel()) { - task.onCancel(); - task.cancel(true); - } - mService.mTasks.remove(id); - for (OnSaveProgressListener o : mService.mProgressListeners) { - o.onDataUpdated(); - } - } - - public Set getAllIds() { - return mService.mTasks.keySet(); - } - - public PausableAsyncTask.ExStatus getTaskStatus(int id) { - SaveTask task = mService.mTasks.get(id); - PausableAsyncTask.ExStatus status = task.getExStatus(); - if (status == PausableAsyncTask.ExStatus.RUNNING && !task.isStarted) { - status = PausableAsyncTask.ExStatus.PENDING; - } - return status; - } - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return new SaveServiceBinder(this); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/services/ScheduledService.java b/app/src/main/java/org/nv95/openmanga/services/ScheduledService.java deleted file mode 100644 index 3d630ffa..00000000 --- a/app/src/main/java/org/nv95/openmanga/services/ScheduledService.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.nv95.openmanga.services; - -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.BitmapFactory; -import android.os.AsyncTask; -import android.os.Build; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.BuildConfig; -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.NewChaptersActivity; -import org.nv95.openmanga.helpers.MangaSaveHelper; -import org.nv95.openmanga.helpers.NotificationHelper; -import org.nv95.openmanga.helpers.ScheduleHelper; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.items.MangaUpdateInfo; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.AppUpdatesProvider; -import org.nv95.openmanga.providers.FavouritesProvider; -import org.nv95.openmanga.providers.NewChaptersProvider; -import org.nv95.openmanga.utils.FileLogger; -import org.nv95.openmanga.utils.NetworkUtils; -import org.nv95.openmanga.utils.OneShotNotifier; - -/** - * Created by nv95 on 18.03.16. - */ -public class ScheduledService extends Service { - - private static final int INTERVAL_CHECK_APP_UPDATE = 12; - - private ScheduleHelper mScheduleHelper; - private boolean mChaptersCheckEnabled; - private int mChaptersCheckInterval; - private boolean mChapterCheckWifiOnly; - private boolean mChapterCheckSave; - private boolean mAutoUpdate; - - @Override - public void onCreate() { - super.onCreate(); - mScheduleHelper = new ScheduleHelper(this); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - mChaptersCheckEnabled = prefs.getBoolean("chupd", false); - mChaptersCheckInterval = Integer.parseInt(prefs.getString("chupd.interval", "12")); - mChapterCheckWifiOnly = prefs.getBoolean("chupd.wifionly", false); - mChapterCheckSave = prefs.getBoolean("chupd.save", false); - //noinspection ConstantConditions - mAutoUpdate = BuildConfig.SELFUPDATE_ENABLED && prefs.getBoolean("autoupdate", true); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (NetworkUtils.checkConnection(this, mChapterCheckWifiOnly)) { - new BackgroundTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - stopSelf(); - } - return START_NOT_STICKY; - } - - private class BackgroundTask extends AsyncTask { - - @Override - protected MangaUpdateInfo[] doInBackground(Void... params) { - try { - int delay = mScheduleHelper.getActionIntervalHours(ScheduleHelper.ACTION_CHECK_APP_UPDATES); - if (mAutoUpdate && (delay < 0 || delay >= INTERVAL_CHECK_APP_UPDATE)) { - publishProgress(new AppUpdatesProvider().getLatestAny()); - mScheduleHelper.actionDone(ScheduleHelper.ACTION_CHECK_APP_UPDATES); - } - } catch (Exception e) { - e.printStackTrace(); - } - try { - int delay = mScheduleHelper.getActionIntervalHours(ScheduleHelper.ACTION_CHECK_NEW_CHAPTERS); - if (mChaptersCheckEnabled && (delay < 0 || delay >= mChaptersCheckInterval)) { - MangaUpdateInfo[] res = NewChaptersProvider.getInstance(ScheduledService.this).checkForNewChapters(); - if (mChapterCheckSave) { - final FavouritesProvider favs = FavouritesProvider.getInstance(ScheduledService.this); - final MangaSaveHelper saveHelper = new MangaSaveHelper(ScheduledService.this); - MangaList mangas = favs.getList(0, 0, 0); - for (MangaUpdateInfo o : res) { - try { - MangaInfo manga = mangas.getById(o.mangaId); - if (manga != null) { - MangaSummary summary = favs.getDetailedInfo(manga); - if (summary != null) { - saveHelper.saveLast(summary, o.getNewChapters()); - } - } - } catch (Exception e) { - FileLogger.getInstance().report("AUTOSAVE", e); - } - } - } - mScheduleHelper.actionDone(ScheduleHelper.ACTION_CHECK_NEW_CHAPTERS); - return res; - } else { - return null; - } - } catch (Exception e) { - return null; - } - } - - @Override - protected void onPostExecute(MangaUpdateInfo[] mangaUpdates) { - super.onPostExecute(mangaUpdates); - if (mangaUpdates != null && mangaUpdates.length != 0) { - int sum = 0; - for (MangaUpdateInfo o : mangaUpdates) { - sum += (o.chapters - o.lastChapters); - } - - Notification.Builder builder = new Notification.Builder(ScheduledService.this) - .setSmallIcon(R.drawable.ic_stat_star) - .setContentTitle(getString(R.string.new_chapters)) - .setContentIntent(PendingIntent.getActivity(ScheduledService.this, 1, - new Intent(ScheduledService.this, NewChaptersActivity.class), 0)) - .setTicker(getString(R.string.new_chapters)) - .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) - .setContentText(String.format(getString(R.string.new_chapters_count), sum)); - Notification notification; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - Notification.InboxStyle inboxStyle = new Notification.InboxStyle(builder); - for (MangaUpdateInfo o : mangaUpdates) { - inboxStyle.addLine((o.chapters - o.lastChapters) + " - " + o.mangaName); - } - inboxStyle.setSummaryText(String.format(getString(R.string.new_chapters_count), sum)); - notification = inboxStyle.build(); - } else { - //noinspection deprecation - notification = builder.getNotification(); - } - notification.flags |= Notification.FLAG_AUTO_CANCEL; - new OneShotNotifier(ScheduledService.this).notify(678, notification); - } - SyncService.syncDelayed(ScheduledService.this.getApplicationContext()); - stopSelf(); - } - - @Override - protected void onProgressUpdate(AppUpdatesProvider.AppUpdateInfo... values) { - super.onProgressUpdate(values); - if (values[0] == null || !values[0].isActual()) { - return; - } - new NotificationHelper(ScheduledService.this) - .title(R.string.app_update_avaliable) - .text(getString(R.string.app_name) + " " + values[0].getVersionName()) - .icon(R.drawable.ic_stat_update) - .autoCancel() - .image(R.mipmap.ic_launcher) - .intentService(new Intent(ScheduledService.this, UpdateService.class).putExtra("url", values[0].getUrl()), 555) - .notifyOnce(555, R.string.app_update_avaliable, values[0].getVersionCode()); - } - - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/services/SyncService.java b/app/src/main/java/org/nv95/openmanga/services/SyncService.java deleted file mode 100644 index bbbf7f16..00000000 --- a/app/src/main/java/org/nv95/openmanga/services/SyncService.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.nv95.openmanga.services; - -import android.app.AlarmManager; -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.helpers.SyncHelper; -import org.nv95.openmanga.items.RESTResponse; -import org.nv95.openmanga.utils.NetworkUtils; - - -/** - * Created by admin on 10.07.17. - */ - -public class SyncService extends IntentService { - - public static final String SYNC_EVENT = "org.nv95.openmanga.SYNC_EVENT"; - public static final int MSG_UNAUTHORIZED = 0; - public static final int MSG_HIST_STARTED = 1; - public static final int MSG_HIST_FINISHED = 2; - public static final int MSG_HIST_FAILED = 3; - public static final int MSG_FAV_STARTED = 4; - public static final int MSG_FAV_FINISHED = 5; - public static final int MSG_FAV_FAILED = 6; - - public SyncService() { - super(SyncService.class.getName()); - } - - @Override - public void onCreate() { - super.onCreate(); - } - - @Override - protected void onHandleIntent(@Nullable Intent intent) { - - SyncHelper syncHelper = SyncHelper.get(this); - if (!syncHelper.isAuthorized()) { - sendMessage(MSG_UNAUTHORIZED, null); - return; - } - if (syncHelper.isHistorySyncEnabled()) { - sendMessage(MSG_HIST_STARTED, null); - RESTResponse resp = syncHelper.syncHistory(); - if (resp.isSuccess()) { - syncHelper.setHistorySynced(); - sendMessage(MSG_HIST_FINISHED, null); - } else { - if (resp.getResponseCode() == RESTResponse.RC_INVALID_TOKEN) { - sendMessage(MSG_UNAUTHORIZED, null); - return; - } - sendMessage(MSG_HIST_FAILED, resp.getMessage()); - } - } - if (syncHelper.isFavouritesSyncEnabled()) { - sendMessage(MSG_FAV_STARTED, null); - RESTResponse resp = syncHelper.syncFavourites(); - if (resp.isSuccess()) { - syncHelper.setFavouritesSynced(); - sendMessage(MSG_FAV_FINISHED, null); - } else { - if (resp.getResponseCode() == RESTResponse.RC_INVALID_TOKEN) { - sendMessage(MSG_UNAUTHORIZED, null); - return; - } - sendMessage(MSG_FAV_FAILED, resp.getMessage()); - } - } - } - - private void sendMessage(int what, @Nullable String msg) { - Intent intent = new Intent(); - intent.setAction(SYNC_EVENT); - intent.putExtra("what", what); - if (msg != null) { - intent.putExtra("message", msg); - } - sendBroadcast(intent); - } - - public static void start(Context context) { - context.startService(new Intent(context, SyncService.class)); - } - - public static void syncDelayed(final Context context) { - new Handler().postDelayed( - new Runnable() { - public void run() { - sync(context); - } - }, - 300); - } - - private static void sync(Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - SyncHelper syncHelper = SyncHelper.get(context); - if (!syncHelper.isAuthorized() || !(syncHelper.isHistorySyncEnabled() || syncHelper.isFavouritesSyncEnabled())) { - return; - } - if (!NetworkUtils.checkConnection(context, prefs.getBoolean("sync.wifionly", false))) { - return; - } - int interval = 12; - try { - interval = Integer.parseInt(prefs.getString("sync.interval", "12")); - } catch (NumberFormatException e) { - e.printStackTrace(); - } - if (interval != -1) { - long intervalMs = AlarmManager.INTERVAL_HOUR * interval; - long lastSync = Math.min(syncHelper.getLastHistorySync(), syncHelper.getLastFavouritesSync()); - if (lastSync + intervalMs <= System.currentTimeMillis()) { - start(context); - } - } - - } - -} diff --git a/app/src/main/java/org/nv95/openmanga/services/UpdateService.java b/app/src/main/java/org/nv95/openmanga/services/UpdateService.java deleted file mode 100644 index 5c040cf6..00000000 --- a/app/src/main/java/org/nv95/openmanga/services/UpdateService.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.nv95.openmanga.services; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.IBinder; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.BuildConfig; -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.NotificationHelper; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -/** - * Created by nv95 on 20.02.16. - */ -public class UpdateService extends Service { - - private static final int NOTIFY_ID = 344; - - private NotificationHelper mNotificationHelper; - - public static void start(Context context, String url) { - if (BuildConfig.SELFUPDATE_ENABLED) { - context.startService(new Intent(context, UpdateService.class) - .putExtra("url", url)); - } - } - - @Override - public void onCreate() { - super.onCreate(); - mNotificationHelper = new NotificationHelper(this).highPriority(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - String url; - if (intent != null && (url = intent.getStringExtra("url")) != null) { - mNotificationHelper.icon(android.R.drawable.stat_sys_download) - .indeterminate() - .title(R.string.app_name) - .image(R.mipmap.ic_launcher) - .text(R.string.update); - startForeground(NOTIFY_ID, mNotificationHelper.notification()); - new DownloadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url); - } else { - stopSelf(); - } - return super.onStartCommand(intent, flags, startId); - } - - private class DownloadTask extends AsyncTask { - - @Override - protected File doInBackground(String... params) { - InputStream input = null; - OutputStream output = null; - HttpURLConnection connection = null; - File destination = null; - try { - final URL url = new URL(params[0]); - destination = new File(getExternalFilesDir("temp"), - params[0].substring(params[0].lastIndexOf('/') + 1)); - connection = (HttpURLConnection) url.openConnection(); - connection.connect(); - - // expect HTTP 200 OK, so we don't mistakenly save error report - // instead of the file - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - return null; - } - - // this will be useful to display download percentage - // might be -1: server did not report the length - int fileLength = connection.getContentLength(); - - // download the file - input = connection.getInputStream(); - output = new FileOutputStream(destination); - - byte data[] = new byte[4096]; - long total = 0; - int count; - while ((count = input.read(data)) != -1) { - // allow canceling with back button - if (isCancelled()) { - input.close(); - output.close(); - destination.delete(); - return null; - } - total += count; - // publishing the progress.... - if (fileLength > 0) // only if total length is known - publishProgress((int) (total * 100 / fileLength)); - output.write(data, 0, count); - } - } catch (Exception e) { - if (destination != null) { - destination.delete(); - destination = null; - } - } finally { - try { - if (output != null) - output.close(); - if (input != null) - input.close(); - } catch (IOException ignored) { - } - if (connection != null) - connection.disconnect(); - } - return destination; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - mNotificationHelper.progress(values[0], 100).update(NOTIFY_ID); - } - - @Override - protected void onPostExecute(File file) { - super.onPostExecute(file); - stopForeground(false); - stopSelf(); - if (file != null) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); - mNotificationHelper - .noProgress() - .icon(R.drawable.ic_stat_update) - .text(R.string.click_to_install) - .intentActivity(intent, 666) - .autoCancel() - .update(NOTIFY_ID, R.string.done); - } else { - mNotificationHelper - .noProgress() - .icon(R.drawable.ic_stat_error) - .text(R.string.loading_error) - .update(NOTIFY_ID); - } - } - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/OnTipsActionListener.java b/app/src/main/java/org/nv95/openmanga/shelf/OnTipsActionListener.java new file mode 100644 index 00000000..2a03beaa --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/OnTipsActionListener.java @@ -0,0 +1,14 @@ +package org.nv95.openmanga.shelf; + +import android.support.annotation.IdRes; + +/** + * Created by koitharu on 29.01.18. + */ + +public interface OnTipsActionListener { + + void onTipActionClick(@IdRes int actionId); + + void onTipDismissed(@IdRes int actionId); +} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfAdapter.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfAdapter.java new file mode 100644 index 00000000..3c98d698 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfAdapter.java @@ -0,0 +1,290 @@ +package org.nv95.openmanga.shelf; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.DataViewHolder; +import org.nv95.openmanga.common.Dismissible; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.core.models.ListHeader; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.models.UserTip; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.mangalist.favourites.FavouritesActivity; +import org.nv95.openmanga.mangalist.history.HistoryActivity; +import org.nv95.openmanga.preview.PreviewActivity; +import org.nv95.openmanga.reader.ReaderActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 21.12.17. + */ + +public final class ShelfAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private final OnTipsActionListener mActionListener; + + public ShelfAdapter(OnTipsActionListener listener) { + mDataset = new ArrayList<>(); + mActionListener = listener; + setHasStableIds(true); + } + + void updateData(ArrayList data) { + mDataset.clear(); + mDataset.addAll(data); + notifyDataSetChanged(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, @ShelfItemType int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + switch (viewType) { + case ShelfItemType.TYPE_HEADER: + return new HeaderHolder(inflater.inflate(R.layout.header_group_button, parent, false)); + case ShelfItemType.TYPE_ITEM_DEFAULT: + return new MangaHolder(inflater.inflate(R.layout.item_manga, parent, false)); + case ShelfItemType.TYPE_RECENT: + return new RecentHolder(inflater.inflate(R.layout.item_recent, parent, false)); + case ShelfItemType.TYPE_ITEM_SMALL: + return new MangaHolder(inflater.inflate(R.layout.item_manga_small, parent, false)); + case ShelfItemType.TYPE_TIP: + return new TipHolder(inflater.inflate(R.layout.item_tip, parent, false)); + default: + throw new AssertionError("Unknown viewType"); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof TipHolder) { + ((TipHolder) holder).bind((UserTip) mDataset.get(position)); + } else if (holder instanceof HeaderHolder) { + ListHeader item = (ListHeader) mDataset.get(position); + if (item.text != null) { + ((HeaderHolder) holder).textView.setText(item.text); + } else if (item.textResId != 0) { + ((HeaderHolder) holder).textView.setText(item.textResId); + } else { + ((HeaderHolder) holder).textView.setText(null); + } + holder.itemView.setTag(item.extra); + } else if (holder instanceof MangaHolder) { + MangaHeader item = (MangaHeader) mDataset.get(position); + ImageUtils.setThumbnail(((MangaHolder) holder).imageViewThumbnail, item.thumbnail, MangaProvider.getDomain(item.provider)); + ((MangaHolder) holder).textViewTitle.setText(item.name); + holder.itemView.setTag(item); + if (holder instanceof RecentHolder) { + MangaHistory history = (MangaHistory) item; + ((RecentHolder) holder).textViewSubtitle.setText(item.summary); + ((RecentHolder) holder).textViewStatus.setText(ResourceUtils.formatTimeRelative(history.updatedAt)); + } + } + + } + + @ShelfItemType + @Override + public int getItemViewType(int position) { + Object item = mDataset.get(position); + if (item instanceof ListHeader) { + return ShelfItemType.TYPE_HEADER; + } else if (item instanceof UserTip) { + return ShelfItemType.TYPE_TIP; + } else if (item instanceof MangaHistory) { + return ShelfItemType.TYPE_RECENT; + } else if (item instanceof MangaFavourite) { + return ShelfItemType.TYPE_ITEM_DEFAULT; + } else if (item instanceof MangaHeader) { + return ShelfItemType.TYPE_ITEM_SMALL; + } else { + throw new AssertionError("Unknown viewType"); + } + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + Object item = mDataset.get(position); + if (item instanceof MangaHeader) { + return ((MangaHeader) item).id; + } else if (item instanceof ListHeader) { + final String text = ((ListHeader) item).text; + return text != null ? text.hashCode() : ((ListHeader) item).textResId; + } else if (item instanceof UserTip) { + return ((UserTip) item).title.hashCode() + ((UserTip) item).content.hashCode(); + } else { + throw new AssertionError("Unknown viewType"); + } + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder instanceof MangaHolder) { + ImageUtils.recycle(((MangaHolder) holder).imageViewThumbnail); + } + super.onViewRecycled(holder); + } + + class HeaderHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final TextView textView; + final Button buttonMore; + + HeaderHolder(View itemView) { + super(itemView); + textView = itemView.findViewById(R.id.textView); + buttonMore = itemView.findViewById(R.id.button_more); + buttonMore.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + final Object extra = itemView.getTag(); + if (extra == null) { + return; + } + final Context context = v.getContext(); + if (extra.equals(ShelfContent.SECTION_HISTORY)) { + context.startActivity(new Intent(context, HistoryActivity.class)); + } else if (extra instanceof Integer) { + context.startActivity(new Intent(context, FavouritesActivity.class) + .putExtra("category_id", (Integer) extra)); + } + } + } + + class TipHolder extends DataViewHolder implements View.OnClickListener, Dismissible { + + private final TextView textViewTitle; + private final TextView textViewContent; + private final ImageView imageViewIcon; + private final Button buttonAction; + private final Button buttonDismiss; + + TipHolder(View itemView) { + super(itemView); + textViewTitle = itemView.findViewById(android.R.id.text1); + textViewContent = itemView.findViewById(android.R.id.text2); + buttonAction = itemView.findViewById(android.R.id.button1); + buttonDismiss = itemView.findViewById(android.R.id.closeButton); + imageViewIcon = itemView.findViewById(android.R.id.icon); + buttonAction.setOnClickListener(this); + buttonDismiss.setOnClickListener(this); + } + + @Override + public void bind(UserTip userTip) { + super.bind(userTip); + textViewTitle.setText(userTip.title); + textViewContent.setText(userTip.content); + if (userTip.hasIcon()) { + imageViewIcon.setImageResource(userTip.icon); + imageViewIcon.setVisibility(View.VISIBLE); + } else { + imageViewIcon.setVisibility(View.GONE); + } + if (userTip.hasAction()) { + buttonAction.setText(userTip.actionText); + buttonAction.setTag(userTip.actionId); + buttonAction.setVisibility(View.VISIBLE); + } else { + buttonAction.setVisibility(View.GONE); + } + buttonDismiss.setVisibility(userTip.hasDismissButton() ? View.VISIBLE : View.GONE); + } + + boolean isDismissible() { + final UserTip data = getData(); + return data != null && data.isDismissible(); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case android.R.id.button1: + Object tag = v.getTag(); + if (tag != null && tag instanceof Integer) { + mActionListener.onTipActionClick((Integer) tag); + } + case android.R.id.closeButton: + dismiss(); + break; + } + } + + @Override + public void dismiss() { + Object tag = buttonAction.getTag(); + if (tag != null && tag instanceof Integer) { + mActionListener.onTipDismissed((Integer) tag); + } + mDataset.remove(getAdapterPosition()); + notifyDataSetChanged(); + } + } + + class MangaHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final ImageView imageViewThumbnail; + final TextView textViewTitle; + + MangaHolder(View itemView) { + super(itemView); + imageViewThumbnail = itemView.findViewById(R.id.imageViewThumbnail); + textViewTitle = itemView.findViewById(R.id.textViewTitle); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + MangaHeader mangaHeader = (MangaHeader) itemView.getTag(); + final Context context = view.getContext(); + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", mangaHeader)); + } + } + + class RecentHolder extends MangaHolder { + + final TextView textViewStatus; + final TextView textViewSubtitle; + + RecentHolder(View itemView) { + super(itemView); + textViewStatus = itemView.findViewById(R.id.textView_status); + textViewSubtitle = itemView.findViewById(R.id.textView_subtitle); + itemView.findViewById(R.id.button_continue).setOnClickListener(this); + } + + @Override + public void onClick(View view) { + if (view.getId() == R.id.button_continue) { + MangaHeader mangaHeader = (MangaHeader) itemView.getTag(); + final Context context = view.getContext(); + context.startActivity(new Intent(context.getApplicationContext(), ReaderActivity.class) + .setAction(ReaderActivity.ACTION_READING_CONTINUE) + .putExtra("manga", mangaHeader)); + } else { + super.onClick(view); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfContent.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfContent.java new file mode 100644 index 00000000..3ea8cfc3 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfContent.java @@ -0,0 +1,50 @@ +package org.nv95.openmanga.shelf; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.models.UserTip; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Created by koitharu on 21.12.17. + */ + +class ShelfContent { + + static final int SECTION_HISTORY = 2; + + @Nullable + MangaHistory recent; + + @NonNull + final ArrayList tips; + + @NonNull + final ArrayList history; + + @NonNull + final HashMap> favourites; + + @NonNull + final ArrayList recommended; + + ShelfContent() { + tips = new ArrayList<>(4); + history = new ArrayList<>(); + favourites = new HashMap<>(); + recommended = new ArrayList<>(); + recent = null; + } + + public boolean isEmpty() { + return recent == null && tips.isEmpty() && history.isEmpty() && favourites.isEmpty() && recommended.isEmpty(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfFragment.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfFragment.java new file mode 100644 index 00000000..7d1ccdd6 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfFragment.java @@ -0,0 +1,164 @@ +package org.nv95.openmanga.shelf; + +import android.app.LoaderManager; +import android.content.Intent; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +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 org.nv95.openmanga.AppBaseFragment; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.Dismissible; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.utils.ThemeUtils; +import org.nv95.openmanga.schedule.JobSchedulerCompat; +import org.nv95.openmanga.tools.settings.SettingsActivity; +import org.nv95.openmanga.updchecker.UpdatesCheckService; + +/** + * Created by koitharu on 21.12.17. + */ + +public final class ShelfFragment extends AppBaseFragment implements LoaderManager.LoaderCallbacks { + + private RecyclerView mRecyclerView; + private ShelfAdapter mAdapter; + private int mColumnCount; + private boolean mWasPaused; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mColumnCount = 12; + mWasPaused = false; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return super.onCreateView(inflater, container, R.layout.recyclerview); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = view.findViewById(R.id.recyclerView); + mRecyclerView.setHasFixedSize(true); + new ItemTouchHelper(new DismissCallback()).attachToRecyclerView(mRecyclerView); + mRecyclerView.setClipToPadding(false); + mRecyclerView.setPadding( + mRecyclerView.getPaddingLeft(), + mRecyclerView.getPaddingTop(), + mRecyclerView.getPaddingRight(), + mRecyclerView.getPaddingBottom() + ThemeUtils.getAttrSizePx(mRecyclerView.getContext(), android.R.attr.actionBarSize) + ); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (ResourceUtils.isLandscapeTablet(getResources())) { + mColumnCount = 24; + } + mAdapter = new ShelfAdapter((OnTipsActionListener) getActivity()); + GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), mColumnCount); + layoutManager.setSpanSizeLookup(new ShelfSpanSizeLookup(mAdapter, mColumnCount)); + mRecyclerView.addItemDecoration(new ShelfItemSpaceDecoration(ResourceUtils.dpToPx(getResources(), 4), mAdapter, mColumnCount)); + mRecyclerView.setLayoutManager(layoutManager); + mRecyclerView.setAdapter(mAdapter); + getLoaderManager().initLoader(0, null, this).forceLoad(); + mWasPaused = false; + } + + @Override + public Loader onCreateLoader(int i, Bundle bundle) { + return new ShelfLoader(getActivity(), mColumnCount); + } + + @Override + public void onLoadFinished(Loader loader, ShelfContent content) { + ShelfUpdater.update(mAdapter, content); + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.options_shelf, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_shelf_settings: + startActivity(new Intent(getActivity(), SettingsActivity.class) + .setAction(SettingsActivity.ACTION_SETTINGS_SHELF)); + return true; + case R.id.action_check_updates: + Snackbar.make(mRecyclerView, R.string.checking_new_chapters, Snackbar.LENGTH_SHORT) + .show(); + UpdatesCheckService.runForce(getActivity()); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onPause() { + mWasPaused = true; + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + if (mWasPaused) { + getLoaderManager().restartLoader(0, null, this).forceLoad(); + mWasPaused = false; + } + } + + @Override + public void scrollToTop() { + mRecyclerView.smoothScrollToPosition(0); + } + + private class DismissCallback extends ItemTouchHelper.SimpleCallback { + + DismissCallback() { + super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT); + } + + @Override + public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + return viewHolder instanceof ShelfAdapter.TipHolder && ((ShelfAdapter.TipHolder) viewHolder).isDismissible() ? + super.getSwipeDirs(recyclerView, viewHolder) : 0; + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + if (viewHolder instanceof Dismissible) { + ((Dismissible) viewHolder).dismiss(); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfItemSpaceDecoration.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfItemSpaceDecoration.java new file mode 100644 index 00000000..4c319416 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfItemSpaceDecoration.java @@ -0,0 +1,38 @@ +package org.nv95.openmanga.shelf; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by koitharu on 24.12.17. + */ + +public class ShelfItemSpaceDecoration extends RecyclerView.ItemDecoration { + + private final int mSpacing; + private final int mMaxSpanSize; + private final RecyclerView.Adapter mAdapter; + + public ShelfItemSpaceDecoration(int spacing, RecyclerView.Adapter adapter, int maxSpanSize) { + mSpacing = spacing; + mAdapter = adapter; + mMaxSpanSize = maxSpanSize; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + final int position = parent.getChildAdapterPosition(view); + final int itemType = mAdapter.getItemViewType(position); + switch (itemType) { + case ShelfItemType.TYPE_RECENT: + case ShelfItemType.TYPE_TIP: + case ShelfItemType.TYPE_ITEM_DEFAULT: + case ShelfItemType.TYPE_ITEM_SMALL: + outRect.left = mSpacing; + outRect.right = mSpacing; + outRect.bottom = mSpacing; + outRect.top = mSpacing; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfItemType.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfItemType.java new file mode 100644 index 00000000..6a5d38ce --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfItemType.java @@ -0,0 +1,20 @@ +package org.nv95.openmanga.shelf; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by koitharu on 24.12.17. + */ + +@Retention(RetentionPolicy.SOURCE) +@IntDef({ShelfItemType.TYPE_ITEM_DEFAULT, ShelfItemType.TYPE_ITEM_SMALL, ShelfItemType.TYPE_HEADER, ShelfItemType.TYPE_TIP, ShelfItemType.TYPE_RECENT}) +public @interface ShelfItemType { + int TYPE_ITEM_DEFAULT = 0; + int TYPE_ITEM_SMALL = 1; + int TYPE_TIP = 2; + int TYPE_HEADER = 3; + int TYPE_RECENT = 4; +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfLoader.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfLoader.java new file mode 100644 index 00000000..9c55538c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfLoader.java @@ -0,0 +1,111 @@ +package org.nv95.openmanga.shelf; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.models.MangaHistory; +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.models.UserTip; +import org.nv95.openmanga.core.storage.FlagsStorage; +import org.nv95.openmanga.core.storage.db.CategoriesRepository; +import org.nv95.openmanga.core.storage.db.CategoriesSpecification; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; +import org.nv95.openmanga.core.storage.db.FavouritesSpecification; +import org.nv95.openmanga.core.storage.db.HistoryRepository; +import org.nv95.openmanga.core.storage.db.HistorySpecification; +import org.nv95.openmanga.core.storage.settings.AppSettings; +import org.nv95.openmanga.core.storage.settings.ShelfSettings; + +import java.util.ArrayList; + +/** + * Created by koitharu on 21.12.17. + */ + +public class ShelfLoader extends AsyncTaskLoader { + + private final int mColumnCount; + + ShelfLoader(Context context, int columnCount) { + super(context); + mColumnCount = columnCount; + } + + @Override + public ShelfContent loadInBackground() { + final ShelfContent content = new ShelfContent(); + final ShelfSettings settings = AppSettings.get(getContext()).shelfSettings; + //tips + //TODO wizard + //history + final HistoryRepository historyRepository = HistoryRepository.get(getContext()); + int len = mColumnCount / 3 * settings.getMaxHistoryRows(); + if (settings.isRecentEnabled()) { + len++; + } + final ArrayList history = historyRepository.query(new HistorySpecification().orderByDate(true).limit(len)); + if (history != null && !history.isEmpty()) { + if (settings.isRecentEnabled()) { + content.recent = history.get(0); + history.remove(0); + } + if (settings.isHistoryEnabled() && !history.isEmpty()) { + content.history.addAll(history); + } + } + //favourites + if (settings.isFavouritesEnabled()) { + final CategoriesRepository categoriesRepository = CategoriesRepository.get(getContext()); + ArrayList categories = categoriesRepository.query(new CategoriesSpecification().orderByDate(true)); + if (categories != null) { + if (categories.isEmpty()) { + Category defaultCategory = Category.createDefault(getContext()); + categories.add(defaultCategory); + categoriesRepository.add(defaultCategory); + ShelfSettings.onCategoryAdded(getContext(), defaultCategory); + } else { + categories = settings.getEnabledCategories(categories); + final FavouritesRepository favouritesRepository = FavouritesRepository.get(getContext()); + for (Category category : categories) { + len = mColumnCount / 2 * settings.getMaxFavouritesRows(); + ArrayList favourites = favouritesRepository.query(new FavouritesSpecification().orderByDate(true).category(category.id).limit(len)); + if (favourites != null && !favourites.isEmpty()) { + content.favourites.put(category, favourites); + } + } + } + } + } + //TODO + if (content.isEmpty()) { + content.tips.add(new UserTip( + getContext().getString(R.string.shelf_is_empty), + getContext().getString(R.string.nothing_here_yet), + R.drawable.ic_discover_green, + R.string.discover, + R.id.action_discover + ).addFlag(UserTip.FLAG_NO_DISMISSIBLE)); + } + final FlagsStorage flagsStorage = FlagsStorage.get(getContext()); + if (flagsStorage.isWizardRequired()) { + content.tips.add(0, new UserTip( + getContext().getString(R.string.welcome), + getContext().getString(R.string.first_run_tip), + R.drawable.ic_wizard_blue, + R.string._continue, + R.id.action_wizard + ).addFlag(UserTip.FLAG_DISMISS_BUTTON)); + } + return content; + } + + @Deprecated + private static int getOptimalCells(int items, int columns) { + if (items <= columns) { + return items; + } + return items - items % columns; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfSpanSizeLookup.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfSpanSizeLookup.java new file mode 100644 index 00000000..43b4170c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfSpanSizeLookup.java @@ -0,0 +1,35 @@ +package org.nv95.openmanga.shelf; + +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; + +/** + * Created by koitharu on 24.12.17. + */ + +public class ShelfSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { + + private final int mMaxSpans; + private final RecyclerView.Adapter mAdapter; + + public ShelfSpanSizeLookup(RecyclerView.Adapter adapter, int maxSpans) { + mAdapter = adapter; + mMaxSpans = maxSpans; + } + + @Override + public int getSpanSize(int position) { + switch (mAdapter.getItemViewType(position)) { + case ShelfItemType.TYPE_ITEM_DEFAULT: + return 4; + case ShelfItemType.TYPE_ITEM_SMALL: + return 3; + case ShelfItemType.TYPE_TIP: + case ShelfItemType.TYPE_RECENT: + case ShelfItemType.TYPE_HEADER: + return mMaxSpans; + default: + return 1; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/shelf/ShelfUpdater.java b/app/src/main/java/org/nv95/openmanga/shelf/ShelfUpdater.java new file mode 100644 index 00000000..a5ef2a3e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/shelf/ShelfUpdater.java @@ -0,0 +1,44 @@ +package org.nv95.openmanga.shelf; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.Category; +import org.nv95.openmanga.core.models.ListHeader; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaHistory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by koitharu on 24.12.17. + */ + +public final class ShelfUpdater { + + public static void update(ShelfAdapter adapter, ShelfContent content) { + ArrayList dataset = new ArrayList<>(content.tips); + if (content.recent != null || !content.history.isEmpty()) { + dataset.add(new ListHeader(R.string.action_history, ShelfContent.SECTION_HISTORY)); + if (content.recent != null) { + dataset.add(content.recent); + } + for (MangaHistory o : content.history) { + dataset.add(MangaHeader.from(o)); + } + } + for (Category category : content.favourites.keySet()) { + List favourites = content.favourites.get(category); + if (favourites != null && !favourites.isEmpty()) { + dataset.add(new ListHeader(category.name, category.id)); + dataset.addAll(favourites); + } + } + if (!content.recommended.isEmpty()) { + dataset.add(new ListHeader(R.string.recommendations, null /*TODO*/)); + dataset.addAll(content.recommended); + } + dataset.trimToSize(); + adapter.updateData(dataset); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/LocalRemoveService.java b/app/src/main/java/org/nv95/openmanga/storage/LocalRemoveService.java new file mode 100644 index 00000000..ef77f97f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/LocalRemoveService.java @@ -0,0 +1,89 @@ +package org.nv95.openmanga.storage; + +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.SavedChapter; +import org.nv95.openmanga.core.models.SavedManga; +import org.nv95.openmanga.core.models.SavedPage; +import org.nv95.openmanga.core.storage.db.SavedChaptersRepository; +import org.nv95.openmanga.core.storage.db.SavedChaptersSpecification; +import org.nv95.openmanga.core.storage.db.SavedMangaRepository; +import org.nv95.openmanga.core.storage.db.SavedPagesRepository; +import org.nv95.openmanga.core.storage.db.SavedPagesSpecification; +import org.nv95.openmanga.core.storage.files.SavedPagesStorage; + +import java.util.ArrayList; +import java.util.List; + +public final class LocalRemoveService extends IntentService { + + private static final String EXTRA_MANGA = "saved_manga"; + private static final String EXTRA_CHAPTERS = "saved_chapters"; + private static final String EXTRA_REMOVE_MANGA = "remove_manga"; + + public LocalRemoveService() { + super("LocalRemove"); + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + if (intent == null) return; + try { + final SavedMangaRepository mangaRepository = SavedMangaRepository.get(this); + final SavedChaptersRepository chaptersRepository = SavedChaptersRepository.get(this); + + final SavedManga manga = mangaRepository.find(intent.getParcelableExtra(EXTRA_MANGA)); + assert manga != null; + final ArrayList chapters = new ArrayList<>(); + if (intent.hasExtra(EXTRA_CHAPTERS)) { + final ArrayList chList = intent.getParcelableArrayListExtra(EXTRA_CHAPTERS); + for (MangaChapter ch : chList) { + chapters.add(SavedChapter.from(ch, manga.id)); + } + } else { + chapters.addAll(chaptersRepository.query(new SavedChaptersSpecification().manga(manga))); + } + + final SavedPagesRepository pagesRepository = SavedPagesRepository.get(this); + final SavedPagesStorage pagesStorage = new SavedPagesStorage(manga); + + for (SavedChapter chapter : chapters) { + final List pages = pagesRepository.query(new SavedPagesSpecification(chapter)); + for (SavedPage page : pages) { + pagesStorage.remove(page); + pagesRepository.remove(page); + } + chaptersRepository.remove(chapter); + } + if (intent.getBooleanExtra(EXTRA_REMOVE_MANGA, false)) { + mangaRepository.remove(manga); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void start(Context context, MangaHeader manga, @Nullable ArrayList chapters, boolean removeManga) { + final Intent intent = new Intent(context, LocalRemoveService.class); + intent.putExtra(EXTRA_MANGA, manga); + if (chapters != null) { + intent.putExtra(EXTRA_CHAPTERS, chapters); + } + intent.putExtra(EXTRA_REMOVE_MANGA, removeManga); + context.startService(intent); + } + + public static void start(Context context, MangaHeader manga, MangaChapter chapter) { + start(context, manga, CollectionsUtils.arrayListOf(chapter), false); + } + + public static void start(Context context, MangaHeader manga) { + start(context, manga, null, true); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/SaveRequest.java b/app/src/main/java/org/nv95/openmanga/storage/SaveRequest.java new file mode 100644 index 00000000..516ff1b7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/SaveRequest.java @@ -0,0 +1,56 @@ +package org.nv95.openmanga.storage; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.util.SparseBooleanArray; + +import org.nv95.openmanga.core.models.MangaChapter; +import org.nv95.openmanga.core.models.MangaChaptersList; +import org.nv95.openmanga.core.models.MangaDetails; + +/** + * Created by koitharu on 25.01.18. + */ + +public final class SaveRequest { + + public final MangaDetails manga; + public final MangaChaptersList chapters; + + public SaveRequest(MangaDetails manga, MangaChaptersList chapters) { + this.manga = manga; + this.chapters = chapters; + } + + public SaveRequest(MangaDetails manga, MangaChapter oneChapter) { + this.manga = manga; + this.chapters = new MangaChaptersList(1); + chapters.add(oneChapter); + } + + public SaveRequest(MangaDetails manga, SparseBooleanArray selection) { + this.manga = manga; + this.chapters = new MangaChaptersList(); + for (int i = 0; i < manga.chapters.size(); i++) { + if (selection.get(i, false)) { + this.chapters.add(manga.chapters.get(i)); + } + } + } + + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(2); + bundle.putParcelable("manga", manga); + bundle.putParcelableArrayList("chapters", chapters); + return bundle; + } + + @NonNull + public static SaveRequest from(Bundle bundle) { + return new SaveRequest( + bundle.getParcelable("manga"), + new MangaChaptersList(bundle.getParcelableArrayList("chapters")) + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/SaveService.java b/app/src/main/java/org/nv95/openmanga/storage/SaveService.java new file mode 100644 index 00000000..85a610f4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/SaveService.java @@ -0,0 +1,217 @@ +package org.nv95.openmanga.storage; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ServiceCompat; + +import org.nv95.openmanga.AsyncService; +import org.nv95.openmanga.BuildConfig; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.NotificationHelper; +import org.nv95.openmanga.common.TwoLevelProgress; +import org.nv95.openmanga.common.utils.BroadcastUtils; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.models.SavedChapter; +import org.nv95.openmanga.core.models.SavedManga; +import org.nv95.openmanga.core.models.SavedPage; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.storage.db.SavedChaptersRepository; +import org.nv95.openmanga.core.storage.db.SavedMangaRepository; +import org.nv95.openmanga.core.storage.db.SavedPagesRepository; +import org.nv95.openmanga.core.storage.files.SavedPagesStorage; +import org.nv95.openmanga.storage.downloaders.Downloader; +import org.nv95.openmanga.storage.downloaders.SimplePageDownloader; + +import java.io.File; +import java.util.ArrayList; + +/** + * Created by koitharu on 25.01.18. + */ + +public final class SaveService extends AsyncService implements Downloader.Callback { + + private static final int RESULT_OK = 0; + private static final int RESULT_CANCELLED = 1; + private static final int RESULT_ERROR_UNKNOWN = -1; + private static final int RESULT_ERROR_WRITE_DIR = -2; + private static final int RESULT_ERROR_NETWORK = -3; + + public static final String ACTION_MANGA_SAVE_START = "org.nv95.openmanga.ACTION_MANGA_SAVE_START"; + public static final String ACTION_MANGA_SAVE_CANCEL = "org.nv95.openmanga.ACTION_MANGA_SAVE_CANCEL"; + public static final String ACTION_MANGA_SAVE_PAUSE = "org.nv95.openmanga.ACTION_MANGA_SAVE_PAUSE"; + public static final String ACTION_MANGA_SAVE_RESUME = "org.nv95.openmanga.ACTION_MANGA_SAVE_RESUME"; + + private NotificationHelper mNotificationHelper; + + @Override + public void onCreate() { + super.onCreate(); + mNotificationHelper = new NotificationHelper(this, 0, "save", R.string.saving_manga); + } + + @Override + public boolean onNewIntent(@NonNull String action, @NonNull Bundle extras) { + switch (action) { + case ACTION_MANGA_SAVE_START: + startBackground(SaveRequest.from(extras)); + return true; + case ACTION_MANGA_SAVE_CANCEL: + mNotificationHelper.setIndeterminate(); + mNotificationHelper.setText(R.string.cancelling); + mNotificationHelper.clearActions(); + mNotificationHelper.update(); + cancelBackground(); + return true; + default: + return false; + } + } + + @Override + public boolean onStopService() { + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE); + if (!isCancelled()) { + mNotificationHelper.update(); + } + return true; + } + + @Override + public void onPreExecute(SaveRequest request) { + mNotificationHelper.nextId(); + mNotificationHelper.setTitle(request.manga.name); + mNotificationHelper.setText(R.string.saving_manga); + mNotificationHelper.setIndeterminate(); + mNotificationHelper.setImage(ImageUtils.getCachedImage(request.manga.thumbnail)); + mNotificationHelper.setIcon(android.R.drawable.stat_sys_download); + mNotificationHelper.addCancelAction(PendingIntent.getService(this, mNotificationHelper.getId() + 1, + new Intent(this, SaveService.class).setAction(SaveService.ACTION_MANGA_SAVE_CANCEL), 0)); + mNotificationHelper.setOngoing(); + startForeground(mNotificationHelper.getId(), mNotificationHelper.get()); + mNotificationHelper.update(); + } + + @Override + public int doInBackground(SaveRequest request) { + try { + final TwoLevelProgress progress = new TwoLevelProgress(); + final MangaProvider provider = MangaProvider.get(this, request.manga.provider); + final SavedMangaRepository mangaRepository = SavedMangaRepository.get(this); + final SavedChaptersRepository chaptersRepository = SavedChaptersRepository.get(this); + final SavedPagesRepository pagesRepository = SavedPagesRepository.get(this); + int total = -1; + int saved = 0; + int totalChapters = request.chapters.size(); + //save manga info + final File rootDir = new File(getExternalFilesDir("saved"), String.valueOf(request.manga.id)); + if (!rootDir.exists() && (!rootDir.mkdirs() || !rootDir.canWrite())) { + return RESULT_ERROR_WRITE_DIR; + } + final SavedManga savedManga = SavedManga.from(request.manga, rootDir); + final SavedPagesStorage pagesStorage = new SavedPagesStorage(savedManga); + mangaRepository.addOrUpdate(savedManga); + //loop for each chapter + progress.setSecondMax(totalChapters); + for (int i = 0; i < totalChapters; i++) { + progress.setSecondPos(i); + progress.setFirstPos(0); + final SavedChapter chapter = SavedChapter.from(request.chapters.get(i), savedManga.id); + chaptersRepository.addOrUpdate(chapter); + setProgress(progress.getPercent(), 100, chapter); + final ArrayList pages = provider.getPages(chapter.url); + final int totalPages = pages.size(); + progress.setFirstMax(totalPages); + for (int j = 0; j < totalPages; j++) { + progress.setFirstPos(j); + setProgress(progress.getPercent(), 100, null); + final SavedPage page = SavedPage.from(pages.get(j), chapter.id, j); + final File dest = pagesStorage.getFile(page); + final Downloader downloader = new SimplePageDownloader(page, dest, provider); + downloader.setCallback(this); + downloader.run(); + if (isCancelled()) { + //TODO remove chapter + return RESULT_CANCELLED; + } + if (downloader.isSuccess()) { + pagesRepository.addOrUpdate(page); + } else { + //try again + Thread.sleep(500); + j--; + } + } + BroadcastUtils.sendDownloadDoneBroadcast(SaveService.this, chapter); + } + return RESULT_OK; + } catch (Exception e) { + e.printStackTrace(); + return RESULT_ERROR_UNKNOWN; + } + } + + @Override + public void onPostExecute(SaveRequest request, int result) { + switch (result) { + case RESULT_OK: + mNotificationHelper.setIcon(android.R.drawable.stat_sys_download_done); + mNotificationHelper.setText(getResources().getQuantityString(R.plurals.chapters_saved, request.chapters.size(), request.chapters.size())); + break; + case RESULT_CANCELLED: + mNotificationHelper.dismiss(); + return; + case RESULT_ERROR_WRITE_DIR: + mNotificationHelper.setIcon(R.drawable.ic_stat_error); + mNotificationHelper.setText(R.string.cannot_create_file); + break; + case RESULT_ERROR_NETWORK: + mNotificationHelper.setIcon(R.drawable.ic_stat_error); + mNotificationHelper.setText(R.string.network_error); + break; + case RESULT_ERROR_UNKNOWN: + mNotificationHelper.setIcon(R.drawable.ic_stat_error); + mNotificationHelper.setText(R.string.error_occurred); + break; + default: + if (BuildConfig.DEBUG) { + throw new AssertionError("Unknown result: " + result); + } + } + mNotificationHelper.clearActions(); + mNotificationHelper.setAutoCancel(); + mNotificationHelper.removeProgress(); + mNotificationHelper.update(); + } + + @Override + public void onProgressUpdate(int progress, int max, @Nullable Object extra) { + if (max == -1) { + mNotificationHelper.setIndeterminate(); + } else { + mNotificationHelper.setProgress(progress, max); + } + mNotificationHelper.update(); + } + + @Override + public boolean isPaused() { + return false; + } + + public static void start(Context context, SaveRequest request) { + context.startService(new Intent(context, SaveService.class) + .setAction(SaveService.ACTION_MANGA_SAVE_START) + .putExtras(request.toBundle())); + } + + private static void cancel(Context context) { + context.startService(new Intent(context, SaveService.class) + .setAction(SaveService.ACTION_MANGA_SAVE_CANCEL)); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/SavedMangaActivity.java b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaActivity.java new file mode 100644 index 00000000..0f951530 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaActivity.java @@ -0,0 +1,127 @@ +package org.nv95.openmanga.storage; + +import android.app.LoaderManager; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.AnimationUtils; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.MenuUtils; +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.storage.db.SavedMangaRepository; +import org.nv95.openmanga.core.storage.db.SavedMangaSpecification; + +import java.util.ArrayList; + +/** + * Created by koitharu on 26.01.18. + */ + +public final class SavedMangaActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks> { + + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + private SavedMangaAdapter mAdapter; + private ArrayList mDataset; + private SavedMangaSpecification mSpecifications; + private SavedMangaRepository mMangaRepository; + + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_history); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mProgressBar = findViewById(R.id.progressBar); + mRecyclerView = findViewById(R.id.recyclerView); + mTextViewHolder = findViewById(R.id.textView_holder); + mTextViewHolder.setText(R.string.no_saved_manga); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); + + mSpecifications = new SavedMangaSpecification() + .orderByDate(true); + mMangaRepository = SavedMangaRepository.get(this); + + mDataset = new ArrayList<>(); + mAdapter = new SavedMangaAdapter(mDataset); + mRecyclerView.setAdapter(mAdapter); + + getLoaderManager().initLoader(0, mSpecifications.toBundle(), this).forceLoad(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_saved_manga, menu); + MenuUtils.setRadioCheckable(menu.findItem(R.id.action_sort), R.id.group_sort); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final String orderBy = mSpecifications.getOrderBy(); + if (orderBy != null && orderBy.contains("name")) { + menu.findItem(R.id.sort_name).setChecked(true); + } else { + menu.findItem(R.id.sort_latest).setChecked(true); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.sort_latest: + item.setChecked(true); + mSpecifications.orderByDate(true); + getLoaderManager().restartLoader(0, mSpecifications.toBundle(), this).forceLoad(); + return true; + case R.id.sort_name: + item.setChecked(true); + mSpecifications.orderByName(false); + getLoaderManager().restartLoader(0, mSpecifications.toBundle(), this).forceLoad(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new SavedMangaListLoader(this, SavedMangaSpecification.from(args)); + } + + @Override + public void onLoadFinished(Loader> loader, ListWrapper result) { + mProgressBar.setVisibility(View.GONE); + if (result.isSuccess()) { + final ArrayList list = result.get(); + mDataset.clear(); + mDataset.addAll(list); + mAdapter.notifyDataSetChanged(); + AnimationUtils.setVisibility(mTextViewHolder, mDataset.isEmpty() ? View.VISIBLE : View.GONE); + } else { + Snackbar.make(mRecyclerView, ErrorUtils.getErrorMessage(result.getError()), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/SavedMangaAdapter.java b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaAdapter.java new file mode 100644 index 00000000..d757bb2a --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaAdapter.java @@ -0,0 +1,94 @@ +package org.nv95.openmanga.storage; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ImageUtils; +import org.nv95.openmanga.core.models.SavedManga; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.preview.PreviewActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 26.01.18. + */ + +public final class SavedMangaAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + + SavedMangaAdapter(ArrayList dataset) { + setHasStableIds(true); + mDataset = dataset; + } + + @Override + public MangaHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new MangaHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_manga_list, parent, false)); + } + + @Override + public void onBindViewHolder(MangaHolder holder, int position) { + SavedMangaSummary item = mDataset.get(position); + holder.text1.setText(item.manga.name); + holder.text2.setText(item.savedChapters == -1 ? "" : + holder.itemView.getResources().getQuantityString(R.plurals.chapters_saved, item.savedChapters, item.savedChapters)); + holder.summary.setText(item.manga.genres); + ImageUtils.setThumbnail(holder.imageView, item.manga.thumbnail, MangaProvider.getDomain(item.manga.provider)); + holder.itemView.setTag(item); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).manga.id; + } + + @Override + public void onViewRecycled(MangaHolder holder) { + ImageUtils.recycle(holder.imageView); + super.onViewRecycled(holder); + } + + class MangaHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + final TextView text1; + final TextView text2; + final TextView summary; + final ImageView imageView; + + private MangaHolder(View itemView) { + super(itemView); + text1 = itemView.findViewById(android.R.id.text1); + text2 = itemView.findViewById(android.R.id.text2); + summary = itemView.findViewById(android.R.id.summary); + imageView = itemView.findViewById(R.id.imageView); + + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + final Context context = view.getContext(); + final SavedManga item = mDataset.get(getAdapterPosition()).manga; + switch (view.getId()) { + default: + context.startActivity(new Intent(context.getApplicationContext(), PreviewActivity.class) + .putExtra("manga", item)); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/storage/SavedMangaListLoader.java b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaListLoader.java new file mode 100644 index 00000000..436ff013 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaListLoader.java @@ -0,0 +1,46 @@ +package org.nv95.openmanga.storage; + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.support.annotation.NonNull; + +import org.nv95.openmanga.core.ListWrapper; +import org.nv95.openmanga.core.models.SavedManga; +import org.nv95.openmanga.core.storage.db.SavedChaptersRepository; +import org.nv95.openmanga.core.storage.db.SavedMangaRepository; +import org.nv95.openmanga.core.storage.db.SavedMangaSpecification; + +import java.util.ArrayList; + +/** + * Created by koitharu on 26.01.18. + */ + +final class SavedMangaListLoader extends AsyncTaskLoader> { + + private final SavedMangaSpecification mSpec; + + SavedMangaListLoader(Context context, SavedMangaSpecification specification) { + super(context); + mSpec = specification; + } + + @Override + @NonNull + public ListWrapper loadInBackground() { + try { + ArrayList list = SavedMangaRepository.get(getContext()).query(mSpec); + if (list == null) { + return ListWrapper.badList(); + } + final SavedChaptersRepository chaptersRepository = SavedChaptersRepository.get(getContext()); + final ArrayList result = new ArrayList<>(list.size()); + for (SavedManga o : list) { + result.add(new SavedMangaSummary(o, chaptersRepository.count(o))); + } + return new ListWrapper<>(result); + } catch (Exception e) { + return new ListWrapper<>(e); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/SavedMangaSummary.java b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaSummary.java new file mode 100644 index 00000000..ccee06d4 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/SavedMangaSummary.java @@ -0,0 +1,18 @@ +package org.nv95.openmanga.storage; + +import org.nv95.openmanga.core.models.SavedManga; + +/** + * Created by koitharu on 26.01.18. + */ + +final class SavedMangaSummary { + + public final SavedManga manga; + public final int savedChapters; + + SavedMangaSummary(SavedManga manga, int savedChapters) { + this.manga = manga; + this.savedChapters = savedChapters; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/downloaders/Downloader.java b/app/src/main/java/org/nv95/openmanga/storage/downloaders/Downloader.java new file mode 100644 index 00000000..f0e8174d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/downloaders/Downloader.java @@ -0,0 +1,70 @@ +package org.nv95.openmanga.storage.downloaders; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; + +import java.io.File; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Created by koitharu on 25.01.18. + */ + +public abstract class Downloader implements Runnable { + + private final T mSource; + private final File mDestination; + private final AtomicReference mStatus; + @Nullable + private Callback mCallback; + + public Downloader(@NonNull T source, @NonNull File destination) { + mSource = source; + mDestination = destination; + mStatus = new AtomicReference<>(null); + } + + public Downloader(@NonNull T source, @NonNull String destination) { + this(source, new File(destination)); + } + + public Downloader setCallback(@Nullable Callback callback) { + mCallback = callback; + return this; + } + + @Override + public final void run() { + final boolean result = onDownload(mSource, mDestination); + mStatus.set(result); + } + + public final boolean isCompleted() { + return mStatus.get() != null; + } + + public final boolean isSuccess() { + return Boolean.TRUE.equals(mStatus.get()); + } + + protected boolean isCancelled() { + return mCallback != null && mCallback.isCancelled(); + } + + protected boolean isPaused() { + return mCallback != null && mCallback.isPaused(); + } + + @WorkerThread + protected abstract boolean onDownload(@NonNull T source, @NonNull File destination); + + public interface Callback { + + @WorkerThread + boolean isCancelled(); + + @WorkerThread + boolean isPaused(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/downloaders/SimplePageDownloader.java b/app/src/main/java/org/nv95/openmanga/storage/downloaders/SimplePageDownloader.java new file mode 100644 index 00000000..ab5bc994 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/downloaders/SimplePageDownloader.java @@ -0,0 +1,89 @@ +package org.nv95.openmanga.storage.downloaders; + +import android.support.annotation.NonNull; + +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.providers.MangaProvider; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by koitharu on 25.01.18. + */ + +public class SimplePageDownloader extends Downloader { + + private final MangaProvider mProvider; + + public SimplePageDownloader(@NonNull MangaPage source, @NonNull File destination, MangaProvider provider) { + super(source, destination); + mProvider = provider; + } + + @Override + protected boolean onDownload(@NonNull MangaPage source, @NonNull File destination) { + InputStream input = null; + FileOutputStream output = null; + try { + final String pageUrl = mProvider.getImageUrl(source); + final String domain = MangaProvider.getDomain(source.provider); + final Request request = new Request.Builder() + .url(pageUrl) + .header(NetworkUtils.HEADER_USER_AGENT, NetworkUtils.USER_AGENT_DEFAULT) + .header(NetworkUtils.HEADER_REFERER, "http://" + domain) + .get() + .build(); + final Response response = NetworkUtils.getHttpClient().newCall(request).execute(); + if (!response.isSuccessful()) { + return false; + } + //noinspection ConstantConditions + input = createInputStream(response.body().byteStream()); + output = new FileOutputStream(destination); + final byte[] buffer = new byte[512]; + int length; + while ((length = input.read(buffer)) >= 0) { + output.write(buffer, 0, length); + while (isPaused() && !isCancelled()) { + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } + if (isCancelled()) { + output.close(); + output = null; + destination.delete(); + return false; + } + } + output.flush(); + return true; + } catch (Throwable e) { + e.printStackTrace(); + return false; + } finally { + if (input != null) try { + input.close(); + } catch (IOException ignored) { + } + if (output != null) try { + output.close(); + } catch (IOException ignored) { + } + } + } + + @NonNull + protected InputStream createInputStream(InputStream delegate) { + return new BufferedInputStream(delegate); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/storage/downloaders/SlowNetworkDownloader.java b/app/src/main/java/org/nv95/openmanga/storage/downloaders/SlowNetworkDownloader.java new file mode 100644 index 00000000..ac331c55 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/storage/downloaders/SlowNetworkDownloader.java @@ -0,0 +1,28 @@ +package org.nv95.openmanga.storage.downloaders; + +import android.support.annotation.NonNull; + +import com.nostra13.universalimageloader.core.assist.FlushedInputStream; + +import org.nv95.openmanga.core.models.MangaPage; +import org.nv95.openmanga.core.providers.MangaProvider; + +import java.io.File; +import java.io.InputStream; + +/** + * Created by koitharu on 25.01.18. + */ + +public final class SlowNetworkDownloader extends SimplePageDownloader { + + public SlowNetworkDownloader(@NonNull MangaPage source, @NonNull File destination, MangaProvider provider) { + super(source, destination, provider); + } + + @NonNull + @Override + protected InputStream createInputStream(InputStream delegate) { + return new FlushedInputStream(delegate); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/AccountService.java b/app/src/main/java/org/nv95/openmanga/sync/AccountService.java new file mode 100644 index 00000000..26ca3959 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/AccountService.java @@ -0,0 +1,27 @@ +package org.nv95.openmanga.sync; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.support.annotation.Nullable; + +/** + * Created by koitharu on 18.12.17. + */ + +public class AccountService extends Service { + + private SyncAuthenticator mAuthenticator; + + @Override + public void onCreate() { + // Create a new authenticator object + mAuthenticator = new SyncAuthenticator(this); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/FavouritesContentProvider.java b/app/src/main/java/org/nv95/openmanga/sync/FavouritesContentProvider.java new file mode 100644 index 00000000..eabb5827 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/FavouritesContentProvider.java @@ -0,0 +1,114 @@ +package org.nv95.openmanga.sync; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import org.nv95.openmanga.core.storage.db.StorageHelper; + +/** + * Created by koitharu on 18.12.17. + */ +@Deprecated +public class FavouritesContentProvider extends ContentProvider { + + private static final int MATCH_ALL = 1; + private static final int MATCH_ROW = 2; + private static final int MATCH_DELETED_ALL = 3; + private static final String TABLE_FAVOURITES = "favourites"; + private static final String TABLE_DELETED = "sync_delete"; + public static final String AUTHORITY = "org.nv95.openmanga.favourites"; + private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + + static { + sUriMatcher.addURI(AUTHORITY, "favourites", MATCH_ALL); + sUriMatcher.addURI(AUTHORITY, "favourites/#", MATCH_ROW); + sUriMatcher.addURI(AUTHORITY, "deleted", MATCH_DELETED_ALL); + } + + private StorageHelper mStorageHelper; + + @Override + public boolean onCreate() { + mStorageHelper = new StorageHelper(getContext()); + return true; + } + + @Override + public String getType(@NonNull Uri uri) { + return "vnd.android.cursor.dir/vnd.openmanga.favourites"; + } + + @Override + public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + switch (sUriMatcher.match(uri)) { + case MATCH_DELETED_ALL: + if (TextUtils.isEmpty(selection)) { + selection = "subject = favourites"; + } else { + selection = selection + " AND subject = favourites"; + } + return mStorageHelper.getReadableDatabase().query(TABLE_DELETED, projection, selection, selectionArgs, null, null, sortOrder); + case MATCH_ALL: + break; + case MATCH_ROW: + if (TextUtils.isEmpty(selection)) { + selection = "id = " + uri.getLastPathSegment(); + } else { + selection = selection + " AND id = " + uri.getLastPathSegment(); + } + break; + } + return mStorageHelper.getReadableDatabase().query(TABLE_FAVOURITES, projection, selection, selectionArgs, null, null, sortOrder); + } + + /* + * insert() always returns null (no URI) + */ + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + if (sUriMatcher.match(uri) != MATCH_ALL) throw new IllegalArgumentException(); + long id = mStorageHelper.getWritableDatabase().insert(TABLE_FAVOURITES, null, values); + return id == -1 ? null : ContentUris.withAppendedId(uri, id); + } + + /* + * delete() always returns "no rows affected" (0) + */ + @Override + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + switch (sUriMatcher.match(uri)) { + case MATCH_DELETED_ALL: + return mStorageHelper.getWritableDatabase().delete(TABLE_DELETED, selection, selectionArgs); + case MATCH_ROW: + if (TextUtils.isEmpty(selection)) { + selection = "id = " + uri.getLastPathSegment(); + } else { + selection = selection + " AND id = " + uri.getLastPathSegment(); + } + break; + } + return mStorageHelper.getWritableDatabase().delete(TABLE_FAVOURITES, selection, selectionArgs); + } + + /* + * update() always returns "no rows affected" (0) + */ + public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { + switch (sUriMatcher.match(uri)) { + case MATCH_ROW: + if (TextUtils.isEmpty(selection)) { + selection = "id = " + uri.getLastPathSegment(); + } else { + selection = selection + " AND id = " + uri.getLastPathSegment(); + } + break; + } + return mStorageHelper.getWritableDatabase().update(TABLE_FAVOURITES, values, selection, selectionArgs); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/FavouritesSyncAdapter.java b/app/src/main/java/org/nv95/openmanga/sync/FavouritesSyncAdapter.java new file mode 100644 index 00000000..dce73138 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/FavouritesSyncAdapter.java @@ -0,0 +1,149 @@ +package org.nv95.openmanga.sync; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.ContentValues; +import android.content.Context; +import android.content.SyncResult; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.nv95.openmanga.common.utils.TextUtils; + +import java.io.IOException; + +/** + * Created by koitharu on 18.12.17. + */ +@Deprecated +public class FavouritesSyncAdapter extends AbstractThreadedSyncAdapter { + + private static final String KEY_LAST_SYNC = "lastsync.favourites"; + private static final String[] PROJECTION = new String[] { + "id", "name", "subtitle", "summary", "provider", "preview", "path", "timestamp", "rating" + }; + private static final String[] PROJECTION_DELETED = new String[]{"manga_id", "timestamp"}; + + private static final Uri URI = Uri.parse("content://" + FavouritesContentProvider.AUTHORITY + "/favourites"); + private static final Uri URI_DELETED = Uri.parse("content://" + FavouritesContentProvider.AUTHORITY + "/deleted"); + + public FavouritesSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + public FavouritesSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { + super(context, autoInitialize, allowParallelSyncs); + } + + @Override + public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient provider, SyncResult syncResult) { + Cursor cursor = null; + try { + //prepare + final AccountManager accountManager = AccountManager.get(getContext()); + final String token = accountManager.blockingGetAuthToken(account, SyncAuthenticator.TOKEN_DEFAULT, true); + final SyncClient client = new SyncClient(token); + String t = accountManager.getUserData(account, KEY_LAST_SYNC); + long lastSync = 0; + if (t != null) { + try { + lastSync = Long.parseLong(t); + } catch (NumberFormatException ne) { + ne.printStackTrace(); + } + } + //get new/updated + cursor = provider.query(URI, PROJECTION, "timestamp > ?", new String[]{String.valueOf(lastSync)}, null); + if (cursor == null) return; + JSONArray updated = new JSONArray(); + if (cursor.moveToFirst()) { + do { + JSONObject jobj = new JSONObject(); + JSONObject manga = new JSONObject(); + manga.put("id", cursor.getInt(0)); + manga.put("name", cursor.getString(1)); + manga.put("subtitle", TextUtils.notNull(cursor.getString(2))); + manga.put("summary", TextUtils.notNull(cursor.getString(3))); + manga.put("provider", cursor.getString(4)); + manga.put("preview", cursor.getString(5)); + manga.put("path", cursor.getString(6)); + manga.put("rating", cursor.getInt(8)); + jobj.put("manga", manga); + jobj.put("timestamp", cursor.getLong(7)); + updated.put(jobj); + } while (cursor.moveToNext()); + } + cursor.close(); + //get deleted + JSONArray deleted = new JSONArray(); + cursor = provider.query(URI_DELETED, PROJECTION_DELETED, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + do { + JSONObject jobj = new JSONObject(); + jobj.put("mangaId", cursor.getInt(0)); + jobj.put("timestamp", cursor.getLong(1)); + deleted.put(jobj); + } while (cursor.moveToNext()); + } + //send to server + RESTResponse response = client.pushFavourites(updated, deleted, lastSync); + if (!response.isSuccess()) { + syncResult.stats.numIoExceptions++; + return; + } + //insert new/update + updated = response.getData().getJSONArray("updated"); + int len = updated.length(); + for (int i=0;i ?", new String[]{String.valueOf(lastSync)}, null); + if (cursor == null) return; + JSONArray updated = new JSONArray(); + if (cursor.moveToFirst()) { + do { + JSONObject jobj = new JSONObject(); + JSONObject manga = new JSONObject(); + manga.put("id", cursor.getInt(0)); + manga.put("name", cursor.getString(1)); + manga.put("subtitle", TextUtils.notNull(cursor.getString(2))); + manga.put("summary", TextUtils.notNull(cursor.getString(3))); + manga.put("provider", cursor.getString(4)); + manga.put("preview", cursor.getString(5)); + manga.put("path", cursor.getString(6)); + manga.put("rating", cursor.getInt(12)); + jobj.put("manga", manga); + jobj.put("timestamp", cursor.getLong(7)); + jobj.put("size", cursor.getInt(8)); + jobj.put("chapter", cursor.getInt(9)); + jobj.put("page", cursor.getInt(10)); + jobj.put("isweb", cursor.getInt(11)); + updated.put(jobj); + } while (cursor.moveToNext()); + } + cursor.close(); + //get deleted + JSONArray deleted = new JSONArray(); + cursor = provider.query(URI_DELETED, PROJECTION_DELETED, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + do { + JSONObject jobj = new JSONObject(); + jobj.put("manga_id", cursor.getInt(0)); + jobj.put("timestamp", cursor.getLong(1)); + deleted.put(jobj); + } while (cursor.moveToNext()); + } + //send to server + RESTResponse response = client.pushHistory(updated, deleted, lastSync); + if (!response.isSuccess()) { + syncResult.stats.numIoExceptions++; + return; + } + //insert new/update + updated = response.getData().getJSONArray("updated"); + int len = updated.length(); + for (int i=0;i Toast.makeText(mContext, R.string.allowed_only_one_account, Toast.LENGTH_SHORT).show()); + return result; + } + + final Intent intent = new Intent(mContext, SyncAuthActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) { + return null; + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { + + // Extract the username and password from the Account Manager, and ask + // the server for an appropriate AuthToken. + final AccountManager am = AccountManager.get(mContext); + + String authToken = am.peekAuthToken(account, authTokenType); + + // Lets give another try to authenticate the user + if (TextUtils.isEmpty(authToken)) { + final String password = am.getPassword(account); + if (password != null) { + authToken = SyncClient.authenticate(account.name, password); + } + } + + // If we get an authToken - we return it + if (!TextUtils.isEmpty(authToken)) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + result.putString(AccountManager.KEY_AUTHTOKEN, authToken); + return result; + } + + return addAccount(response, account.type, authTokenType, new String[0], options); + } + + + @Override + public String getAuthTokenLabel(String s) { + return null; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) { + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) { + return null; + } + + private boolean isAccountExists() { + return AccountManager.get(mContext).getAccountsByType(ACCOUNT_TYPE).length != 0; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/SyncClient.java b/app/src/main/java/org/nv95/openmanga/sync/SyncClient.java new file mode 100644 index 00000000..5bae3d01 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/SyncClient.java @@ -0,0 +1,137 @@ +package org.nv95.openmanga.sync; + +import android.os.Build; +import android.support.annotation.Nullable; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.nv95.openmanga.BuildConfig; +import org.nv95.openmanga.common.utils.network.NetworkUtils; + +import java.util.ArrayList; + +/** + * Created by koitharu on 19.12.17. + */ +@Deprecated +public class SyncClient { + + private final String mToken; + + public SyncClient(String token) { + mToken = token; + } + + public RESTResponse detachDevice(int id) { + return NetworkUtils.restQuery( + BuildConfig.SYNC_URL + "/user", + mToken, + "DELETE", + "id", + String.valueOf(id) + ); + } + + public ArrayList getAttachedDevices() throws JSONException, InvalidTokenException { + ArrayList list = new ArrayList<>(); + RESTResponse resp = NetworkUtils.restQuery( + BuildConfig.SYNC_URL + "/user", + mToken, + "GET", + "self", + "0" + ); + if (!resp.isSuccess()) { + if (resp.getResponseCode() == RESTResponse.RC_INVALID_TOKEN) { + throw new InvalidTokenException(); + } + return null; + } + JSONArray devices = resp.getData().getJSONArray("devices"); + int len = devices.length(); + for (int i = 0; i < len; i++) { + JSONObject o = devices.getJSONObject(i); + list.add(new SyncDevice( + o.getInt("id"), + o.getString("device"), + o.getLong("created_at") + )); + } + return list; + } + + public RESTResponse pushHistory(JSONArray updated, JSONArray deleted, long lastSync) throws InvalidTokenException { + RESTResponse resp = NetworkUtils.restQuery( + BuildConfig.SYNC_URL + "/history", + mToken, + "POST", + "timestamp", + String.valueOf(lastSync), + "updated", + updated.toString(), + "deleted", + deleted.toString() + ); + if (!resp.isSuccess()) { + if (resp.getResponseCode() == RESTResponse.RC_INVALID_TOKEN) { + throw new InvalidTokenException(); + } + } + return resp; + } + + public RESTResponse pushFavourites(JSONArray updated, JSONArray deleted, long lastSync) throws InvalidTokenException { + RESTResponse resp = NetworkUtils.restQuery( + BuildConfig.SYNC_URL + "/favourites", + mToken, + "POST", + "timestamp", + String.valueOf(lastSync), + "updated", + updated.toString(), + "deleted", + deleted.toString() + ); + if (!resp.isSuccess()) { + if (resp.getResponseCode() == RESTResponse.RC_INVALID_TOKEN) { + throw new InvalidTokenException(); + } + } + return resp; + } + + @Nullable + public static String authenticate(String login, String password) { + RESTResponse response = NetworkUtils.restQuery( + BuildConfig.SYNC_URL + "/user", + null, + "POST", + "login", login, + "password", password, + "device", + getDeviceSummary() + ); + if (response.isSuccess()) { + try { + return response.getData().getString("token"); + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + return null; + } + + public class InvalidTokenException extends IllegalArgumentException { + } + + public static String getDeviceSummary() { + return Build.MANUFACTURER + + ' ' + + Build.MODEL + + " (Android " + + Build.VERSION.RELEASE + + ")"; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/SyncDevice.java b/app/src/main/java/org/nv95/openmanga/sync/SyncDevice.java new file mode 100644 index 00000000..5eac0a1b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/SyncDevice.java @@ -0,0 +1,18 @@ +package org.nv95.openmanga.sync; + +/** + * Created by koitharu on 31.12.17. + */ + +public class SyncDevice { + + public int id; + public String name; + public long created_at; + + public SyncDevice(int id, String name, long created_at) { + this.id = id; + this.name = name; + this.created_at = created_at; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/sync/ui/AppCompatAuthActivity.java b/app/src/main/java/org/nv95/openmanga/sync/ui/AppCompatAuthActivity.java new file mode 100644 index 00000000..5f5db048 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/ui/AppCompatAuthActivity.java @@ -0,0 +1,131 @@ +package org.nv95.openmanga.sync.ui; + +import android.accounts.AccountAuthenticatorActivity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import org.nv95.openmanga.common.utils.ThemeUtils; + +/** + * Created by koitharu on 18.12.17. + */ + +public abstract class AppCompatAuthActivity extends AccountAuthenticatorActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + int mTheme = ThemeUtils.getAppTheme(this); + setTheme(ThemeUtils.getAppThemeRes(mTheme)); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + @NonNull + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } + + public void enableHomeAsUp() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/ui/DevicesAdapter.java b/app/src/main/java/org/nv95/openmanga/sync/ui/DevicesAdapter.java new file mode 100644 index 00000000..1208539c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/ui/DevicesAdapter.java @@ -0,0 +1,70 @@ +package org.nv95.openmanga.sync.ui; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.sync.SyncDevice; +import org.nv95.openmanga.common.utils.ResourceUtils; + +import java.util.ArrayList; + +/** + * Created by koitharu on 19.12.17. + */ + +public class DevicesAdapter extends RecyclerView.Adapter implements View.OnClickListener { + + private final ArrayList mDataset; + private final OnItemClickListener mClickListener; + + public DevicesAdapter(ArrayList dataset, OnItemClickListener clickListener) { + mDataset = dataset; + mClickListener = clickListener; + } + + @Override + public DeviceHolder onCreateViewHolder(ViewGroup parent, int viewType) { + DeviceHolder holder = new DeviceHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_device, parent, false)); + holder.itemView.setOnClickListener(this); + return holder; + } + + @Override + public void onBindViewHolder(DeviceHolder holder, int position) { + SyncDevice item = mDataset.get(position); + holder.textViewDate.setText(ResourceUtils.formatDateTimeRelative(holder.textViewName.getContext(), item.created_at)); + holder.textViewName.setText(item.name); + holder.itemView.setTag(item); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public void onClick(View view) { + mClickListener.onItemClick((SyncDevice) view.getTag()); + } + + class DeviceHolder extends RecyclerView.ViewHolder { + + final TextView textViewName; + final TextView textViewDate; + + DeviceHolder(View itemView) { + super(itemView); + textViewName = itemView.findViewById(R.id.textName); + textViewDate = itemView.findViewById(R.id.textDate); + } + } + + interface OnItemClickListener { + + void onItemClick(SyncDevice item); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/ui/DevicesListActivity.java b/app/src/main/java/org/nv95/openmanga/sync/ui/DevicesListActivity.java new file mode 100644 index 00000000..8b84717d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/ui/DevicesListActivity.java @@ -0,0 +1,159 @@ +package org.nv95.openmanga.sync.ui; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.app.LoaderManager; +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.sync.RESTResponse; +import org.nv95.openmanga.sync.SyncAuthenticator; +import org.nv95.openmanga.sync.SyncClient; +import org.nv95.openmanga.sync.SyncDevice; + +import java.util.ArrayList; + +/** + * Created by koitharu on 18.12.17. + */ + +public class DevicesListActivity extends AppBaseActivity implements LoaderManager.LoaderCallbacks>,DevicesAdapter.OnItemClickListener { + + private AccountManager mAccountManager; + private Account mAccount; + + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + private TextView mTextViewHolder; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_devices); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + mRecyclerView = findViewById(R.id.recyclerView); + mProgressBar = findViewById(R.id.progressBar); + mTextViewHolder = findViewById(R.id.textView_holder); + + mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL)); + + mAccountManager = AccountManager.get(this); + Account[] accounts = mAccountManager.getAccountsByType(SyncAuthenticator.ACCOUNT_TYPE); + if (accounts.length == 0) { + finish(); + return; + } + mAccount = accounts[0]; + + getLoaderManager().initLoader(0, null, this); + getLoaderManager().getLoader(0).forceLoad(); + } + + + @Override + public Loader> onCreateLoader(int i, Bundle bundle) { + AccountManagerFuture future = mAccountManager.getAuthToken(mAccount, SyncAuthenticator.TOKEN_DEFAULT, null, this, null,null); + return new DevicesLoader(this, future); + } + + @Override + public void onLoadFinished(Loader> loader, ArrayList syncDevices) { + mProgressBar.setVisibility(View.GONE); + if (syncDevices.isEmpty()) { + mTextViewHolder.setVisibility(View.VISIBLE); + } else { + DevicesAdapter adapter = new DevicesAdapter(syncDevices, this); + mRecyclerView.setAdapter(adapter); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + @Override + public void onItemClick(final SyncDevice item) { + new AlertDialog.Builder(this) + .setMessage(getString(R.string.device_detach_confirm, item.name)) + .setPositiveButton(R.string.detach, (dialogInterface, i) -> { + mProgressBar.setVisibility(View.VISIBLE); + new DeviceDetachTask(DevicesListActivity.this) + .start(item.id); + }) + .setNegativeButton(android.R.string.cancel, null) + .create().show(); + } + + private static class DevicesLoader extends AsyncTaskLoader> { + + private final AccountManagerFuture mAccountFuture; + + DevicesLoader(Context context, AccountManagerFuture accountFuture) { + super(context); + mAccountFuture = accountFuture; + } + + @Override + public ArrayList loadInBackground() { + try { + String token = mAccountFuture.getResult().getString(AccountManager.KEY_AUTHTOKEN); + return new SyncClient(token).getAttachedDevices(); + } catch (Throwable t) { + t.printStackTrace(); + return null; + } + } + } + + private static class DeviceDetachTask extends WeakAsyncTask { + + private final AccountManagerFuture mAccountFuture; + + DeviceDetachTask(DevicesListActivity activity) { + super(activity); + mAccountFuture = activity.mAccountManager.getAuthToken(activity.mAccount, SyncAuthenticator.TOKEN_DEFAULT, + null, activity, null,null); + } + + @Override + protected RESTResponse doInBackground(Integer... integers) { + try { + String token = mAccountFuture.getResult().getString(AccountManager.KEY_AUTHTOKEN); + return new SyncClient(token).detachDevice(integers[0]); + } catch (Exception e) { + e.printStackTrace(); + return RESTResponse.fromThrowable(e); + } + } + + @Override + protected void onPostExecute(@NonNull DevicesListActivity activity, RESTResponse restResponse) { + if (restResponse.isSuccess()) { + activity.getLoaderManager().getLoader(0).onContentChanged(); + Snackbar.make(activity.mRecyclerView, R.string.device_detached, Snackbar.LENGTH_SHORT).show(); + } else { + activity.mProgressBar.setVisibility(View.GONE); + Snackbar.make(activity.mRecyclerView, restResponse.getMessage(), Snackbar.LENGTH_SHORT).show(); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/sync/ui/SyncAuthActivity.java b/app/src/main/java/org/nv95/openmanga/sync/ui/SyncAuthActivity.java new file mode 100644 index 00000000..4b2e9d6e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/sync/ui/SyncAuthActivity.java @@ -0,0 +1,228 @@ +package org.nv95.openmanga.sync.ui; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.text.TextUtils; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; + +import org.json.JSONException; +import org.nv95.openmanga.BuildConfig; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.network.NetworkUtils; +import org.nv95.openmanga.sync.RESTResponse; +import org.nv95.openmanga.sync.SyncAuthenticator; +import org.nv95.openmanga.sync.SyncClient; + +/** + * Created by koitharu on 18.12.17. + */ + +public class SyncAuthActivity extends AppCompatAuthActivity implements View.OnClickListener { + + private UserLoginTask mAuthTask = null; + private TextInputLayout mLayoutPassword; + private TextInputEditText mEditLogin; + private TextInputEditText mEditPassword; + private Button mButtonSignIn; + private Button mButtonRegisterMode; + private Button mButtonRegister; + private Button mButtonBack; + private View mViewProgress; + private View mViewForm; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_syncauth); + setSupportActionBar(findViewById(R.id.toolbar)); + enableHomeAsUp(); + + mEditLogin = findViewById(R.id.editLogin); + mEditPassword = findViewById(R.id.editPassword); + mLayoutPassword = findViewById(R.id.inputLayoutPassword); + mButtonSignIn = findViewById(R.id.buttonLogin); + mButtonRegister = findViewById(R.id.buttonRegister); + mButtonRegisterMode = findViewById(R.id.buttonRegisterMode); + mButtonBack = findViewById(R.id.buttonBack); + + mEditPassword.setOnEditorActionListener((textView, id, keyEvent) -> { + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + attemptLogin(mButtonRegister.getVisibility() == View.VISIBLE); + return true; + } + return false; + }); + + mButtonRegister.setOnClickListener(this); + mButtonSignIn.setOnClickListener(this); + mButtonRegisterMode.setOnClickListener(this); + mButtonBack.setOnClickListener(this); + + mViewForm = findViewById(R.id.login_form); + mViewProgress = findViewById(R.id.login_progress); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.buttonLogin: + attemptLogin(false); + break; + case R.id.buttonRegister: + attemptLogin(true); + break; + case R.id.buttonRegisterMode: + mButtonSignIn.setVisibility(View.GONE); + mButtonRegister.setVisibility(View.VISIBLE); + mButtonRegisterMode.setVisibility(View.GONE); + mButtonBack.setVisibility(View.VISIBLE); + break; + case R.id.buttonBack: + mButtonSignIn.setVisibility(View.VISIBLE); + mButtonRegister.setVisibility(View.GONE); + mButtonRegisterMode.setVisibility(View.VISIBLE); + mButtonBack.setVisibility(View.GONE); + break; + } + } + + private void attemptLogin(boolean wantRegister) { + if (mAuthTask != null) { + return; + } + mEditLogin.setError(null); + mEditPassword.setError(null); + mLayoutPassword.setError(null); + + String login = mEditLogin.getText().toString().trim(); + String password = mEditPassword.getText().toString(); + + boolean cancel = false; + View focusView = null; + + if (wantRegister && !TextUtils.isEmpty(password) && password.length() < 4) { + mLayoutPassword.setError(getString(R.string.password_too_short)); + focusView = mEditPassword; + cancel = true; + } + + if (TextUtils.isEmpty(login)) { + mEditLogin.setError(getString(R.string.login_required)); + focusView = mEditLogin; + cancel = true; + } + + if (cancel) { + if (focusView != null) { + focusView.requestFocus(); + } + } else { + showProgress(true); + mAuthTask = new UserLoginTask(this, login, password, wantRegister); + mAuthTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void showProgress(final boolean show) { + + int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); + + mViewForm.setVisibility(show ? View.GONE : View.VISIBLE); + mViewForm.animate().setDuration(shortAnimTime).alpha( + show ? 0 : 1).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mViewForm.setVisibility(show ? View.GONE : View.VISIBLE); + } + }); + + mViewProgress.setVisibility(show ? View.VISIBLE : View.GONE); + mViewProgress.animate().setDuration(shortAnimTime).alpha( + show ? 1 : 0).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mViewProgress.setVisibility(show ? View.VISIBLE : View.GONE); + } + }); + } + + private void finishLogin(String login, String password, String token) { + final AccountManager accountManager = AccountManager.get(this); + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, login); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, SyncAuthenticator.ACCOUNT_TYPE); + intent.putExtra(AccountManager.KEY_AUTHTOKEN, token); + final Account account = new Account(login, SyncAuthenticator.ACCOUNT_TYPE); + accountManager.addAccountExplicitly(account, password, null); + accountManager.setAuthToken(account, SyncAuthenticator.TOKEN_DEFAULT, token); + setAccountAuthenticatorResult(intent.getExtras()); + setResult(RESULT_OK, intent); + finish(); + } + + + public static class UserLoginTask extends WeakAsyncTask { + + private final String mLogin; + private final String mPassword; + private final boolean mWantRegister; + + UserLoginTask(SyncAuthActivity authActivity, String login, String password, boolean wantRegister) { + super(authActivity); + mLogin = login; + mPassword = password; + mWantRegister = wantRegister; + } + + @Override + protected RESTResponse doInBackground(Void... params) { + return NetworkUtils.restQuery( + BuildConfig.SYNC_URL + "/user", + null, + mWantRegister ? "PUT" : "POST", + "login", mLogin, + "password", mPassword, + "device", + SyncClient.getDeviceSummary() + ); + } + + @Override + protected void onPostExecute(@NonNull SyncAuthActivity authActivity, RESTResponse response) { + authActivity.mAuthTask = null; + authActivity.showProgress(false); + + if (response.isSuccess()) { + try { + final String token = response.getData().getString("token"); + authActivity.finishLogin(mLogin, mPassword, token); + } catch (JSONException e) { + e.printStackTrace(); + authActivity.mLayoutPassword.setError(authActivity.getString(R.string.error)); + authActivity.mEditPassword.requestFocus(); + } + } else { + authActivity.mLayoutPassword.setError(response.getMessage()); + authActivity.mEditPassword.requestFocus(); + } + } + + + @Override + protected void onTaskCancelled(@NonNull SyncAuthActivity authActivity) { + authActivity.mAuthTask = null; + authActivity.showProgress(false); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/CacheClearTask.java b/app/src/main/java/org/nv95/openmanga/tools/CacheClearTask.java new file mode 100644 index 00000000..c65ba485 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/CacheClearTask.java @@ -0,0 +1,97 @@ +package org.nv95.openmanga.tools; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.FilesystemUtils; + +import java.io.File; +import java.lang.ref.WeakReference; + +/** + * Created by koitharu on 05.02.18. + */ + +final class CacheClearTask extends WeakAsyncTask { + + private final ProgressDialog mProgressDialog; + private final WeakReference mCallbackRef; + + public CacheClearTask(Context context, Callback callback) { + super(context); + mCallbackRef = new WeakReference<>(callback); + mProgressDialog = new ProgressDialog(context); + mProgressDialog.setMessage(context.getString(R.string.cache_clearing)); + if (context instanceof Activity) { + mProgressDialog.setOwnerActivity((Activity) context); + } + mProgressDialog.setCancelable(false); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + mProgressDialog.setIndeterminate(true); + } + + @Override + protected void onPreExecute(@NonNull Context context) { + super.onPreExecute(context); + mProgressDialog.show(); + } + + @Override + protected Long doInBackground(Void... voids) { + try { + final File internalCache = getObject().getCacheDir(); + final File externalCache = getObject().getExternalCacheDir(); + final long size = FilesystemUtils.getFileSize(internalCache) + + FilesystemUtils.getFileSize(externalCache); + publishProgress(0, (int) (size / 1024)); + long removed = clearDir(internalCache, size, 0); + removed += clearDir(externalCache, size, removed); + return size - removed; + } catch (Exception e) { + e.printStackTrace(); + return -1L; + } + } + + @WorkerThread + private long clearDir(@Nullable File dir, long total, long removed) { + if (dir == null || !dir.exists()) { + return 0L; + } + long _removed = removed; + final File[] files = dir.listFiles(); + for (File o : files) { + if (o.isDirectory()) { + _removed = clearDir(o, total, _removed); + } else { + final long length = o.length(); + if (o.delete()) { + _removed += length; + } + publishProgress((int)(removed / 1024), (int) (total / 1024)); + } + } + return _removed; + } + + @Override + protected void onPostExecute(@NonNull Context context, Long aLong) { + super.onPostExecute(context, aLong); + mProgressDialog.dismiss(); + final Callback callback = mCallbackRef.get(); + if (callback != null) { + callback.onCacheSizeChanged(aLong); + } + } + + public interface Callback { + + void onCacheSizeChanged(long newSize); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/StorageStats.java b/app/src/main/java/org/nv95/openmanga/tools/StorageStats.java new file mode 100644 index 00000000..f2f8ddbf --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/StorageStats.java @@ -0,0 +1,16 @@ +package org.nv95.openmanga.tools; + +/** + * Created by koitharu on 02.02.18. + */ + +final class StorageStats { + + public long cacheSize; + public long savedSize; + public long otherSize; + + public long total() { + return cacheSize + savedSize + otherSize; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/StorageStatsLoader.java b/app/src/main/java/org/nv95/openmanga/tools/StorageStatsLoader.java new file mode 100644 index 00000000..332b39e7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/StorageStatsLoader.java @@ -0,0 +1,41 @@ +package org.nv95.openmanga.tools; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.common.utils.FilesystemUtils; +import org.nv95.openmanga.core.models.SavedManga; +import org.nv95.openmanga.core.storage.db.SavedMangaRepository; +import org.nv95.openmanga.core.storage.db.SavedMangaSpecification; +import org.nv95.openmanga.core.storage.files.SavedPagesStorage; + +import java.util.List; + +/** + * Created by koitharu on 02.02.18. + */ + +final class StorageStatsLoader extends AsyncTaskLoader { + + public StorageStatsLoader(Context context) { + super(context); + } + + @Override + public StorageStats loadInBackground() { + final StorageStats stats = new StorageStats(); + stats.cacheSize = FilesystemUtils.getFileSize(getContext().getExternalCacheDir()) + + FilesystemUtils.getFileSize(getContext().getCacheDir()); + final SavedMangaRepository savedRepo = SavedMangaRepository.get(getContext()); + final List manga = savedRepo.query(new SavedMangaSpecification()); + if (manga != null) { + for (SavedManga o : manga) { + SavedPagesStorage storage = SavedPagesStorage.get(getContext(), o); + if (storage != null) { + stats.savedSize += storage.size(); + } + } + } + return stats; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/ToolsFragment.java b/app/src/main/java/org/nv95/openmanga/tools/ToolsFragment.java new file mode 100644 index 00000000..a1a10a77 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/ToolsFragment.java @@ -0,0 +1,124 @@ +package org.nv95.openmanga.tools; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.LoaderManager; +import android.content.Context; +import android.content.Intent; +import android.content.Loader; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.widget.NestedScrollView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.nv95.openmanga.AppBaseFragment; +import org.nv95.openmanga.BuildConfig; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.common.utils.TextUtils; +import org.nv95.openmanga.common.utils.ThemeUtils; +import org.nv95.openmanga.tools.settings.SettingsHeadersActivity; + +/** + * Created by koitharu on 02.02.18. + */ + +public final class ToolsFragment extends AppBaseFragment implements View.OnClickListener, + LoaderManager.LoaderCallbacks, CacheClearTask.Callback { + + private static final int LOADER_STORAGE_STATS = 0; + + private NestedScrollView mScrollView; + private TextView mTextViewStorageTotal; + private TextView mTextViewStorageCache; + private TextView mTextViewStorageManga; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return super.onCreateView(inflater, container, R.layout.fragment_tools); + } + + @Override + @SuppressLint("SetTextI18n") + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mScrollView = view.findViewById(R.id.scrollView); + mScrollView.setClipToPadding(false); + mScrollView.setPadding( + mScrollView.getPaddingLeft(), + mScrollView.getPaddingTop(), + mScrollView.getPaddingRight(), + mScrollView.getPaddingBottom() + ThemeUtils.getAttrSizePx(mScrollView.getContext(), android.R.attr.actionBarSize) + ); + mTextViewStorageTotal = view.findViewById(R.id.textView_storage_total); + mTextViewStorageCache = view.findViewById(R.id.textView_storage_cache); + mTextViewStorageManga = view.findViewById(R.id.textView_storage_manga); + + view.findViewById(R.id.action_settings).setOnClickListener(this); + view.findViewById(R.id.button_clear_cache).setOnClickListener(this); + view.findViewById(R.id.button_saved_manga).setOnClickListener(this); + view.findViewById(R.id.textView_about).setText( + view.getContext().getString(R.string.app_name) + " v" + BuildConfig.VERSION_NAME + + "\n" + ResourceUtils.formatDateTime(view.getContext(), BuildConfig.TIMESTAMP) + ); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final Activity activity = getActivity(); + getLoaderManager().initLoader(LOADER_STORAGE_STATS, null, this).forceLoad(); + } + + @Override + public void scrollToTop() { + mScrollView.smoothScrollTo(0, 0); + } + + @Override + public void onClick(View v) { + final Context context = v.getContext(); + switch (v.getId()) { + case R.id.action_settings: + startActivity(new Intent(context, SettingsHeadersActivity.class)); + break; + case R.id.button_clear_cache: + new CacheClearTask(context, this).start(); + break; + case R.id.button_saved_manga: + //TODO + } + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new StorageStatsLoader(getActivity()); + } + + @Override + public void onLoadFinished(Loader loader, StorageStats data) { + mTextViewStorageTotal.setText(TextUtils.formatFileSize(data.total())); + mTextViewStorageCache.setText(TextUtils.formatFileSize(data.cacheSize)); + mTextViewStorageManga.setText(TextUtils.formatFileSize(data.savedSize)); + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + @Override + public void onCacheSizeChanged(long newSize) { + if (newSize == -1) { + Snackbar.make(mScrollView, R.string.error_occurred, Snackbar.LENGTH_SHORT).show(); + } else { + mTextViewStorageCache.setText(TextUtils.formatFileSize(newSize)); + getLoaderManager().getLoader(LOADER_STORAGE_STATS).onContentChanged(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/tools/settings/AppearanceSettingsFragment.java new file mode 100644 index 00000000..70241ee6 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/AppearanceSettingsFragment.java @@ -0,0 +1,25 @@ +package org.nv95.openmanga.tools.settings; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.PreferencesUtils; + +/** + * Created by koitharu on 17.01.18. + */ + +public final class AppearanceSettingsFragment extends PreferenceFragment { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_appearance); + PreferencesUtils.bindSummaryMultiple( + this, + "theme" + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/AuthorizationDialog.java b/app/src/main/java/org/nv95/openmanga/tools/settings/AuthorizationDialog.java new file mode 100644 index 00000000..185ed2f5 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/AuthorizationDialog.java @@ -0,0 +1,186 @@ +package org.nv95.openmanga.tools.settings; + +import android.app.Activity; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.support.v7.app.AppCompatDialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.ProgressBar; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.ErrorUtils; +import org.nv95.openmanga.common.utils.network.CookieStore; +import org.nv95.openmanga.core.ObjectWrapper; +import org.nv95.openmanga.core.providers.MangaProvider; + +/** + * Created by koitharu on 12.01.18. + */ + +public final class AuthorizationDialog extends AppCompatDialogFragment implements View.OnClickListener, + Handler.Callback { + + private ProgressBar mProgressBar; + private TextInputLayout mInputLayoutLogin; + private TextInputLayout mInputLayoutPassword; + private TextInputEditText mEditTextLogin; + private TextInputEditText mEditTextPassword; + private Button mButtonLogin; + private Button mButtonCancel; + + private String mProviderCName; + @Nullable + private AuthTask mAuthTask; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle args = getArguments(); + assert args != null; + mProviderCName = args.getString("provider"); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.dialog_authorization, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mProgressBar = view.findViewById(R.id.progressBar); + mInputLayoutLogin = view.findViewById(R.id.inputLayout_login); + mInputLayoutPassword = view.findViewById(R.id.inputLayout_password); + mEditTextLogin = view.findViewById(R.id.edit_login); + mEditTextPassword = view.findViewById(R.id.edit_password); + mButtonLogin = view.findViewById(R.id.button_login); + mButtonCancel = view.findViewById(R.id.button_cancel); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mButtonCancel.setOnClickListener(this); + mButtonLogin.setOnClickListener(this); + mEditTextPassword.setOnEditorActionListener((textView, id, keyEvent) -> { + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + onClick(mButtonLogin); + return true; + } + return false; + }); + + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.button_cancel: + dismiss(); + break; + case R.id.button_login: + mInputLayoutPassword.setError(null); + final String login = mEditTextLogin.getText().toString().trim(); + final String password = mEditTextPassword.getText().toString(); + if (login.length() == 0) { + mEditTextLogin.requestFocus(); + return; + } + if (password.length() == 0) { + mEditTextPassword.requestFocus(); + return; + } + if (mAuthTask != null && mAuthTask.canCancel()) { + mAuthTask.cancel(true); + } + mAuthTask = new AuthTask(this); + mAuthTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, mProviderCName, login, password); + break; + } + } + + private void setIsReady(boolean isReady) { + mButtonLogin.setEnabled(isReady); + mButtonCancel.setEnabled(isReady); + mInputLayoutLogin.setEnabled(isReady); + mInputLayoutPassword.setEnabled(isReady); + mEditTextLogin.setEnabled(isReady); + mEditTextPassword.setEnabled(isReady); + mProgressBar.setVisibility(isReady ? View.GONE : View.VISIBLE); + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.what == 1) { + final Activity activity = getActivity(); + if (activity != null && activity instanceof Callback) { + ((Callback) activity).onAuthorized(); + } + dismiss(); + return true; + } + return false; + } + + private static final class AuthTask extends WeakAsyncTask> { + + AuthTask(AuthorizationDialog authorizationDialog) { + super(authorizationDialog); + } + + @Override + protected void onPreExecute(@NonNull AuthorizationDialog authorizationDialog) { + authorizationDialog.setIsReady(false); + } + + @Override + protected ObjectWrapper doInBackground(String... strings) { + try { + @SuppressWarnings("ConstantConditions") + final MangaProvider provider = MangaProvider.get(getObject().getContext(), strings[0]); + String result = provider.authorize(strings[1], strings[2]); + return result == null ? ObjectWrapper.badObject() : new ObjectWrapper<>(result); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + protected void onPostExecute(@NonNull AuthorizationDialog authorizationDialog, @NonNull ObjectWrapper data) { + authorizationDialog.setIsReady(true); + authorizationDialog.mAuthTask = null; + if (data.isFailed()) { + authorizationDialog.mInputLayoutPassword.setError( + data.getError() instanceof ObjectWrapper.BadResultException ? + authorizationDialog.getString(R.string.auth_failed) + : authorizationDialog.getString(ErrorUtils.getErrorMessage(data.getError())) + ); + } else { + CookieStore.getInstance().put(MangaProvider.getDomain(authorizationDialog.mProviderCName), data.get()); + final Activity activity = authorizationDialog.getActivity(); + if (activity != null && activity instanceof Callback) { + ((Callback) activity).onAuthorized(); + } + authorizationDialog.dismiss(); + } + } + } + + public interface Callback { + + void onAuthorized(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/MangaUpdatesSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/tools/settings/MangaUpdatesSettingsFragment.java new file mode 100644 index 00000000..934720c9 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/MangaUpdatesSettingsFragment.java @@ -0,0 +1,117 @@ +package org.nv95.openmanga.tools.settings; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceFragment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.CollectionsUtils; +import org.nv95.openmanga.common.utils.PreferencesUtils; +import org.nv95.openmanga.common.utils.ResourceUtils; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; +import org.nv95.openmanga.core.storage.db.FavouritesSpecification; + +import java.util.ArrayList; + +public final class MangaUpdatesSettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_mangaupdates); + PreferencesUtils.bindSummaryMultiple( + this, + "mangaupdates.interval", + "mangaupdates.networktype" + ); + findPreference("mangaupdates.check_now") + .setSummary( + formatDateSummary( + getPreferenceManager().getSharedPreferences().getLong("mangaupdates.last_check", 0) + )); + } + + @Override + public void onStart() { + super.onStart(); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onStop() { + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + super.onStop(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final PreferenceCategory category = (PreferenceCategory) findPreference("mangaupdates.tracked"); + new LoadTrackedTask(category).start(); + final Activity activity = getActivity(); + if (activity instanceof Preference.OnPreferenceClickListener) { + findPreference("mangaupdates.check_now") + .setOnPreferenceClickListener((Preference.OnPreferenceClickListener) activity); + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case "mangaupdates.last_check": + findPreference("mangaupdates.check_now") + .setSummary( + formatDateSummary( + sharedPreferences.getLong(key, 0) + )); + final PreferenceCategory category = (PreferenceCategory) findPreference("mangaupdates.tracked"); + new LoadTrackedTask(category).start(); + break; + } + } + + private String formatDateSummary(long timeMs) { + final String s = timeMs == 0 ? getString(R.string.never) : ResourceUtils.formatTimeRelative(timeMs); + return getString(R.string.last_update_check, s); + } + + private static class LoadTrackedTask extends WeakAsyncTask> { + + LoadTrackedTask(PreferenceCategory preferenceCategory) { + super(preferenceCategory); + } + + @Override + protected ArrayList doInBackground(Void... voids) { + try { + return FavouritesRepository.get(getObject().getContext()) + .query(new FavouritesSpecification()); + } catch (Exception e) { + return CollectionsUtils.empty(); + } + } + + @Override + @SuppressLint("DefaultLocale") + protected void onPostExecute(@NonNull PreferenceCategory category, ArrayList headers) { + final Context context = category.getContext(); + category.removeAll(); + for (MangaFavourite o : headers) { + final Preference p = new Preference(context); + p.setKey("manga_" + o.id); + p.setTitle(o.name); + p.setSummary(String.format("%s %d (+%d)", context.getString(R.string.chapters_count_), o.totalChapters, o.newChapters)); + category.addPreference(p); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/NetworkSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/tools/settings/NetworkSettingsFragment.java new file mode 100644 index 00000000..4b3378fc --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/NetworkSettingsFragment.java @@ -0,0 +1,34 @@ +package org.nv95.openmanga.tools.settings; + +import android.app.Activity; +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.PreferencesUtils; + +/** + * Created by koitharu on 17.01.18. + */ + +public final class NetworkSettingsFragment extends PreferenceFragment { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_network); + PreferencesUtils.bindSummaryMultiple( + this, + "network.usage.show_thumbnails" + ); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Activity activity = getActivity(); + if (activity == null) { + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/ReaderSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/tools/settings/ReaderSettingsFragment.java new file mode 100644 index 00000000..d6b23dc3 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/ReaderSettingsFragment.java @@ -0,0 +1,27 @@ +package org.nv95.openmanga.tools.settings; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.PreferencesUtils; + +/** + * Created by koitharu on 06.02.18. + */ + +public final class ReaderSettingsFragment extends PreferenceFragment { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_reader); + PreferencesUtils.bindSummaryMultiple( + this, + "reader.default_preset", + "reader.scale_mode", + "reader.background" + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsActivity.java b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsActivity.java new file mode 100644 index 00000000..9da2946d --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsActivity.java @@ -0,0 +1,107 @@ +package org.nv95.openmanga.tools.settings; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.view.View; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.TextUtils; +import org.nv95.openmanga.updchecker.JobSetupReceiver; +import org.nv95.openmanga.updchecker.UpdatesCheckService; + +/** + * Created by koitharu on 17.01.18. + */ + +public class SettingsActivity extends AppBaseActivity implements SharedPreferences.OnSharedPreferenceChangeListener, + Preference.OnPreferenceClickListener { + + public static final String ACTION_SETTINGS_APPEARANCE = "org.nv95.openmanga.ACTION_SETTINGS_APPEARANCE"; + public static final String ACTION_SETTINGS_READER = "org.nv95.openmanga.ACTION_SETTINGS_READER"; + public static final String ACTION_SETTINGS_SHELF = "org.nv95.openmanga.ACTION_SETTINGS_SHELF"; + public static final String ACTION_SETTINGS_MANGAUPDATES = "org.nv95.openmanga.ACTION_SETTINGS_MANGAUPDATES"; + + public static final int RESULT_RESTART = Activity.RESULT_FIRST_USER + 1; + + private PreferenceFragment mFragment; + private View mContent; + private SharedPreferences mDefaultPreferences; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + mContent = findViewById(R.id.content); + mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(this); + final String action = TextUtils.notNull(getIntent().getAction()); + switch (action) { + case Intent.ACTION_MANAGE_NETWORK_USAGE: + mFragment = new NetworkSettingsFragment(); + break; + case ACTION_SETTINGS_APPEARANCE: + mFragment = new AppearanceSettingsFragment(); + break; + case ACTION_SETTINGS_READER: + mFragment = new ReaderSettingsFragment(); + break; + case ACTION_SETTINGS_SHELF: + mFragment = new ShelfSettingsFragment(); + break; + case ACTION_SETTINGS_MANGAUPDATES: + mFragment = new MangaUpdatesSettingsFragment(); + break; + default: + finish(); + } + getFragmentManager() + .beginTransaction() + .replace(R.id.content, mFragment) + .commit(); + } + + @Override + protected void onStart() { + super.onStart(); + mDefaultPreferences.registerOnSharedPreferenceChangeListener(this); + } + + @Override + protected void onStop() { + mDefaultPreferences.unregisterOnSharedPreferenceChangeListener(this); + super.onStop(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case "theme": + setResult(RESULT_RESTART); + break; + case "mangaupdates.enabled": + JobSetupReceiver.setup(this); + break; + } + } + + @Override + public boolean onPreferenceClick(Preference preference) { + switch (preference.getKey()) { + case "mangaupdates.check_now": + UpdatesCheckService.runForce(this); + Snackbar.make(mFragment.getView(), R.string.checking_new_chapters, Snackbar.LENGTH_SHORT).show(); + return true; + default: + return false; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsAdapter.java b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsAdapter.java new file mode 100644 index 00000000..5d1be93f --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsAdapter.java @@ -0,0 +1,154 @@ +package org.nv95.openmanga.tools.settings; + +import android.support.annotation.IntDef; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.CrashHandler; +import org.nv95.openmanga.common.Dismissible; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Created by koitharu on 12.01.18. + */ + +public class SettingsAdapter extends RecyclerView.Adapter { + + private final ArrayList mDataset; + private final AdapterView.OnItemClickListener mClickListener; + + SettingsAdapter(ArrayList headers, AdapterView.OnItemClickListener clickListener) { + mDataset = headers; + mClickListener = clickListener; + setHasStableIds(true); + } + + @Override + public PreferenceHolder onCreateViewHolder(ViewGroup parent, @ItemType int viewType) { + switch (viewType) { + case ItemType.TYPE_ITEM_DEFAULT: + return new PreferenceHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_two_lines_icon, parent, false)); + case ItemType.TYPE_TIP: + return new TipHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_tip, parent, false)); + default: + throw new AssertionError("Unknown viewType"); + } + } + + @Override + public void onBindViewHolder(PreferenceHolder holder, int position) { + SettingsHeader item = mDataset.get(position); + holder.text1.setText(item.title); + holder.icon.setImageDrawable(item.icon); + if (item.summary == null) { + holder.text2.setVisibility(View.GONE); + } else { + holder.text2.setText(item.summary); + holder.text2.setVisibility(View.VISIBLE); + } + if (holder instanceof TipHolder) { + ((TipHolder) holder).button.setText(item.actionText); + ((TipHolder) holder).button.setId(item.actionId); + } + } + + @Override + public int getItemViewType(int position) { + return mDataset.get(position).hasAction() ? ItemType.TYPE_TIP : ItemType.TYPE_ITEM_DEFAULT; + + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).title.hashCode(); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + class PreferenceHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + private final ImageView icon; + private final TextView text1; + private final TextView text2; + + PreferenceHolder(View itemView) { + super(itemView); + icon = itemView.findViewById(android.R.id.icon); + text1 = itemView.findViewById(android.R.id.text1); + text2 = itemView.findViewById(android.R.id.text2); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + mClickListener.onItemClick(null, itemView, getAdapterPosition(), getItemId()); + } + } + + class TipHolder extends PreferenceHolder implements Dismissible { + + final Button button; + + TipHolder(View itemView) { + super(itemView); + itemView.setOnClickListener(null); + button = itemView.findViewById(android.R.id.button1); + button.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.action_crash_report: + final CrashHandler crashHandler = CrashHandler.get(); + if (crashHandler != null) { + new AlertDialog.Builder(view.getContext()) + .setTitle(crashHandler.getErrorClassName()) + .setMessage(crashHandler.getErrorMessage() + "\n\n" + crashHandler.getErrorStackTrace()) + .setNegativeButton(R.string.close, null) + .create() + .show(); + } + break; + } + } + + @Override + public void dismiss() { + switch (button.getId()) { + case R.id.action_crash_report: + final CrashHandler crashHandler = CrashHandler.get(); + if (crashHandler != null) { + crashHandler.clear(); + } + break; + } + mDataset.remove(getAdapterPosition()); + notifyDataSetChanged(); + //notifyItemRemoved throws ArrayIndexOutOfBoundsException + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ItemType.TYPE_ITEM_DEFAULT, ItemType.TYPE_TIP}) + public @interface ItemType { + int TYPE_ITEM_DEFAULT = 0; + int TYPE_TIP = 1; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsDecoration.java b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsDecoration.java new file mode 100644 index 00000000..48d7349e --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsDecoration.java @@ -0,0 +1,80 @@ +package org.nv95.openmanga.tools.settings; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.CardView; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import org.nv95.openmanga.common.utils.ResourceUtils; + +/** + * Created by koitharu on 12.01.18. + */ + +final class SettingsDecoration extends RecyclerView.ItemDecoration { + + private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; + + private final Drawable mDivider; + private final int mSpacing; + + private final Rect mBounds = new Rect(); + + SettingsDecoration(Context context) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + mSpacing = ResourceUtils.dpToPx(context.getResources(), 4); + } + + @Override + public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { + if (parent.getLayoutManager() == null || mDivider == null) { + return; + } + canvas.save(); + final int left; + final int right; + if (parent.getClipToPadding()) { + left = parent.getPaddingLeft(); + right = parent.getWidth() - parent.getPaddingRight(); + canvas.clipRect(left, parent.getPaddingTop(), right, + parent.getHeight() - parent.getPaddingBottom()); + } else { + left = 0; + right = parent.getWidth(); + } + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + if (child instanceof CardView) { + continue; + } + parent.getDecoratedBoundsWithMargins(child, mBounds); + final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); + final int top = bottom - mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(canvas); + } + canvas.restore(); + } + + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + if (view instanceof CardView) { + outRect.set(mSpacing, mSpacing, mSpacing, mSpacing); + return; + } + if (mDivider == null) { + outRect.set(0, 0, 0, 0); + return; + } + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsHeader.java b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsHeader.java new file mode 100644 index 00000000..f5602fb3 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsHeader.java @@ -0,0 +1,47 @@ +package org.nv95.openmanga.tools.settings; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.content.ContextCompat; + +/** + * Created by koitharu on 12.01.18. + */ + +public final class SettingsHeader { + + public final int id; + public String title; + public String summary; + public Drawable icon; + + @StringRes + public final int actionText; + @IdRes + public final int actionId; + + public SettingsHeader(@NonNull Context context, int id, @StringRes int title,@DrawableRes int icon) { + this(context, id, title, 0, icon, 0, 0); + } + + public SettingsHeader(@NonNull Context context, int id, @StringRes int title, @StringRes int summary, @DrawableRes int icon) { + this(context, id, title, summary, icon, 0, 0); + } + + public SettingsHeader(@NonNull Context context, int id, @StringRes int title, @StringRes int summary, @DrawableRes int icon, @StringRes int actionText, @IdRes int actionId) { + this.id = id; + this.title = context.getString(title); + this.icon = ContextCompat.getDrawable(context, icon); + this.summary = summary == 0 ? null : context.getString(summary); + this.actionText = actionText; + this.actionId = actionId; + } + + public boolean hasAction() { + return actionText != 0 && actionId != 0; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsHeadersActivity.java b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsHeadersActivity.java new file mode 100644 index 00000000..a41ddfb8 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/SettingsHeadersActivity.java @@ -0,0 +1,93 @@ +package org.nv95.openmanga.tools.settings; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.AdapterView; + +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.OpenMangaApp; +import org.nv95.openmanga.R; +import org.nv95.openmanga.tools.settings.providers.ProvidersSettingsActivity; + +import java.util.ArrayList; + +/** + * Created by koitharu on 12.01.18. + */ + +public final class SettingsHeadersActivity extends AppBaseActivity implements AdapterView.OnItemClickListener { + + private static final int REQUEST_SETTINGS = 12; + + private ArrayList mHeaders; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings_headers); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + + RecyclerView mRecyclerView = findViewById(R.id.recyclerView); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.setHasFixedSize(true); + + mHeaders = new ArrayList<>(); + //mHeaders.add(new SettingsHeader(activity, 0, R.string.general, R.drawable.ic_home_white)); + mHeaders.add(new SettingsHeader(this, 1, R.string.appearance, R.drawable.ic_appearance_white)); + mHeaders.add(new SettingsHeader(this, 2, R.string.manga_catalogues, R.drawable.ic_network_white)); + //mHeaders.add(new SettingsHeader(this, 3, R.string.downloads, R.drawable.ic_download_white)); + mHeaders.add(new SettingsHeader(this, 4, R.string.action_reading_options, R.drawable.ic_read_white)); + mHeaders.add(new SettingsHeader(this, 5, R.string.checking_new_chapters, R.drawable.ic_notify_new_white)); + //mHeaders.add(new SettingsHeader(this, 6, R.string.sync, R.drawable.ic_cloud_sync_white)); + //mHeaders.add(new SettingsHeader(this, 7, R.string.additional, R.drawable.ic_braces_white)); + //mHeaders.add(new SettingsHeader(this, 8, R.string.help, R.drawable.ic_help_white)); + + SettingsAdapter mAdapter = new SettingsAdapter(mHeaders, this); + mRecyclerView.setAdapter(mAdapter); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + SettingsHeader header = mHeaders.get(position); + Intent intent; + switch (header.id) { + case 2: + intent = new Intent(view.getContext(), ProvidersSettingsActivity.class); + break; + case 1: + intent = new Intent(view.getContext(), SettingsActivity.class) + .setAction(SettingsActivity.ACTION_SETTINGS_APPEARANCE); + break; + case 4: + intent = new Intent(view.getContext(), SettingsActivity.class) + .setAction(SettingsActivity.ACTION_SETTINGS_READER); + break; + case 5: + intent = new Intent(view.getContext(), SettingsActivity.class) + .setAction(SettingsActivity.ACTION_SETTINGS_MANGAUPDATES); + break; + default: + return; + } + startActivityForResult(intent, REQUEST_SETTINGS); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_SETTINGS && resultCode == SettingsActivity.RESULT_RESTART) { + new AlertDialog.Builder(this) + .setMessage(R.string.need_restart) + .setNegativeButton(R.string.postpone, null) + .setPositiveButton(R.string.restart, (dialog, which) -> + OpenMangaApp.from(SettingsHeadersActivity.this).restart()) + .create() + .show(); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/ShelfSettingsFragment.java b/app/src/main/java/org/nv95/openmanga/tools/settings/ShelfSettingsFragment.java new file mode 100644 index 00000000..87ef2c2c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/ShelfSettingsFragment.java @@ -0,0 +1,23 @@ +package org.nv95.openmanga.tools.settings; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.common.utils.PreferencesUtils; + +public final class ShelfSettingsFragment extends PreferenceFragment { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pref_shelf); + PreferencesUtils.bindSummaryMultiple( + this, + "shelf.history_rows", + "shelf.favourites_cat_rows", + "shelf.favourites_categories" + ); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/TipsLoader.java b/app/src/main/java/org/nv95/openmanga/tools/settings/TipsLoader.java new file mode 100644 index 00000000..9b3a8f96 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/TipsLoader.java @@ -0,0 +1,39 @@ +package org.nv95.openmanga.tools.settings; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import org.nv95.openmanga.common.CrashHandler; +import org.nv95.openmanga.R; + +import java.util.ArrayList; + +/** + * Created by koitharu on 12.01.18. + */ + +public final class TipsLoader extends AsyncTaskLoader> { + + public TipsLoader(Context context) { + super(context); + } + + @Override + public ArrayList loadInBackground() { + final ArrayList result = new ArrayList<>(4); + //tips + CrashHandler crashHandler = CrashHandler.get(); + if (crashHandler != null && crashHandler.wasCrashed()) { + result.add(new SettingsHeader( + getContext(), + -1, + R.string.error_occurred, + R.string.application_crashed, + R.drawable.ic_bug_red, + R.string.report, + R.id.action_crash_report + )); + } + return result; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/providers/ProvidersAdapter.java b/app/src/main/java/org/nv95/openmanga/tools/settings/providers/ProvidersAdapter.java new file mode 100644 index 00000000..88b8e028 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/providers/ProvidersAdapter.java @@ -0,0 +1,107 @@ +package org.nv95.openmanga.tools.settings.providers; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.v7.widget.AppCompatImageButton; +import android.support.v7.widget.AppCompatImageView; +import android.support.v7.widget.RecyclerView; +import android.util.SparseBooleanArray; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.ProviderHeader; + +import java.util.ArrayList; + +/** + * Created by koitharu on 17.01.18. + */ + +final class ProvidersAdapter extends RecyclerView.Adapter { + + private final OnStartDragListener mDragListener; + private final ArrayList mDataset; + private final SparseBooleanArray mChecks; + + ProvidersAdapter(ArrayList dataset, SparseBooleanArray checks, OnStartDragListener dragListener) { + mDataset = dataset; + mChecks = checks; + mDragListener = dragListener; + setHasStableIds(true); + } + + @Override + public ProviderHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ProviderHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_provider, parent, false)); + } + + @Override + public void onBindViewHolder(ProviderHolder holder, int position) { + holder.checkBox.setChecked(mChecks.get(position, true)); + holder.textView.setText(mDataset.get(position).dName); + } + + @Override + public int getItemCount() { + return mDataset.size(); + } + + @Override + public long getItemId(int position) { + return mDataset.get(position).cName.hashCode(); + } + + @SuppressLint("ClickableViewAccessibility") + class ProviderHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnTouchListener { + + final CheckBox checkBox; + final TextView textView; + final AppCompatImageButton imageButtonSettings; + final AppCompatImageView imageViewReorder; + + ProviderHolder(View itemView) { + super(itemView); + checkBox = itemView.findViewById(android.R.id.checkbox); + textView = itemView.findViewById(android.R.id.text1); + imageButtonSettings = itemView.findViewById(R.id.imageButton_settings); + imageViewReorder = itemView.findViewById(R.id.imageView_reorder); + + imageButtonSettings.setOnClickListener(this); + checkBox.setOnClickListener(this); + imageViewReorder.setOnTouchListener(this); + } + + @Override + public void onClick(View v) { + final int position = getAdapterPosition(); + switch (v.getId()) { + case android.R.id.checkbox: + mChecks.put(position, checkBox.isChecked()); + break; + case R.id.imageButton_settings: + final Context context = v.getContext(); + final String cName = mDataset.get(position).cName; + //TODO start activity + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mDragListener.onStartDrag(this); + return true; + } + return false; + } + } + + interface OnStartDragListener { + void onStartDrag(RecyclerView.ViewHolder viewHolder); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/tools/settings/providers/ProvidersSettingsActivity.java b/app/src/main/java/org/nv95/openmanga/tools/settings/providers/ProvidersSettingsActivity.java new file mode 100644 index 00000000..ae030ca0 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/tools/settings/providers/ProvidersSettingsActivity.java @@ -0,0 +1,91 @@ +package org.nv95.openmanga.tools.settings.providers; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.SparseBooleanArray; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.ProviderHeader; +import org.nv95.openmanga.core.storage.ProvidersStore; +import org.nv95.openmanga.AppBaseActivity; +import org.nv95.openmanga.common.utils.CollectionsUtils; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * Created by koitharu on 17.01.18. + */ + +public final class ProvidersSettingsActivity extends AppBaseActivity implements ProvidersAdapter.OnStartDragListener { + + private ProvidersStore mProvidersStore; + private ProvidersAdapter mAdapter; + private ArrayList mProviders; + private SparseBooleanArray mChecks; + private ItemTouchHelper mItemTouchHelper; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings_providers); + setSupportActionBar(R.id.toolbar); + enableHomeAsUp(); + mProvidersStore = new ProvidersStore(this); + mChecks = new SparseBooleanArray(); + mProviders = mProvidersStore.getAllProvidersSorted(); + int[] disabled = mProvidersStore.getDisabledIds(); + for (int i = 0; i < mProviders.size(); i++) { + if (CollectionsUtils.indexOf(disabled, mProviders.get(i).hashCode()) != -1) { + mChecks.put(i, false); + } + } + final RecyclerView recyclerView = findViewById(R.id.recyclerView); + mAdapter = new ProvidersAdapter(mProviders, mChecks, this); + recyclerView.setAdapter(mAdapter); + + mItemTouchHelper = new ItemTouchHelper(new ReorderCallback()); + mItemTouchHelper.attachToRecyclerView(recyclerView); + } + + @Override + public void onStartDrag(RecyclerView.ViewHolder viewHolder) { + mItemTouchHelper.startDrag(viewHolder); + } + + @Override + protected void onPause() { + mProvidersStore.save(mProviders, mChecks); + super.onPause(); + } + + private class ReorderCallback extends ItemTouchHelper.SimpleCallback { + + + ReorderCallback() { + super(ItemTouchHelper.DOWN | ItemTouchHelper.UP, 0); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + final int fromPosition = viewHolder.getAdapterPosition(); + final int toPosition = target.getAdapterPosition(); + Collections.swap(mProviders, fromPosition, toPosition); + CollectionsUtils.swap(mChecks, fromPosition, toPosition, true); + mAdapter.notifyItemMoved(fromPosition, toPosition); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/updchecker/JobSetupReceiver.java b/app/src/main/java/org/nv95/openmanga/updchecker/JobSetupReceiver.java new file mode 100644 index 00000000..e3097e03 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/updchecker/JobSetupReceiver.java @@ -0,0 +1,94 @@ +package org.nv95.openmanga.updchecker; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.util.Log; + +import org.nv95.openmanga.common.utils.TextUtils; + +import java.util.concurrent.TimeUnit; + +/** + * Created by koitharu on 17.01.18. + */ + +public final class JobSetupReceiver extends BroadcastReceiver { + + private static final int JOB_ID = 10100; + + @Override + public void onReceive(Context context, Intent intent) { + switch (TextUtils.notNull(intent.getAction())) { + case Intent.ACTION_BOOT_COMPLETED: + case "android.intent.action.QUICKBOOT_POWERON": + case "com.htc.intent.action.QUICKBOOT_POWERON": + setup(context); + break; + default: + Log.w("JOB", "Unknown action: " + intent.getAction()); + } + } + + public static void setup(Context context) { + dismiss(context); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final boolean enabled = prefs.getBoolean("mangaupdates.enabled", false); + if (!enabled) { + return; + } + final long interval = TimeUnit.HOURS.toMillis( + Long.parseLong(prefs.getString("mangaupdates.interval", "12")) + ); + final boolean allowMetered = "0".equals(prefs.getString("mangaupdates.networktype", "0")); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ComponentName serviceName = new ComponentName(context, UpdatesCheckJobService.class); + JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceName) + .setPeriodic(interval) + .setRequiredNetworkType(allowMetered ? JobInfo.NETWORK_TYPE_ANY : JobInfo.NETWORK_TYPE_UNMETERED) + .setRequiresDeviceIdle(false) + .setRequiresCharging(false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setRequiresBatteryNotLow(true); + } + JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + if (scheduler != null) { + scheduler.cancel(JOB_ID); + scheduler.schedule(builder.build()); + } + } else { + final AlarmManager manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); + if (manager != null) { + final PendingIntent pendingIntent = PendingIntent.getService(context, JOB_ID, + new Intent(context, UpdatesCheckService.class), 0); + manager.cancel(pendingIntent); + manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + interval, interval, pendingIntent); + } + } + } + + public static void dismiss(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + if (scheduler != null) { + scheduler.cancel(JOB_ID); + } + } else { + final AlarmManager manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); + if (manager != null) { + final PendingIntent pendingIntent = PendingIntent.getService(context, JOB_ID, + new Intent(context, UpdatesCheckService.class), 0); + manager.cancel(pendingIntent); + } + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/updchecker/MangaUpdatesChecker.java b/app/src/main/java/org/nv95/openmanga/updchecker/MangaUpdatesChecker.java new file mode 100644 index 00000000..a20fc9b1 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/updchecker/MangaUpdatesChecker.java @@ -0,0 +1,92 @@ +package org.nv95.openmanga.updchecker; + +import android.content.Context; +import android.preference.PreferenceManager; +import android.support.annotation.WorkerThread; + +import org.nv95.openmanga.core.models.MangaDetails; +import org.nv95.openmanga.core.models.MangaFavourite; +import org.nv95.openmanga.core.models.MangaHeader; +import org.nv95.openmanga.core.models.MangaUpdateInfo; +import org.nv95.openmanga.core.providers.MangaProvider; +import org.nv95.openmanga.core.storage.db.FavouritesRepository; +import org.nv95.openmanga.core.storage.db.FavouritesSpecification; + +import java.util.ArrayList; + +/** + * Created by koitharu on 30.01.18. + */ + +public final class MangaUpdatesChecker { + + static final int COUNT_UNKNOWN = -1; + private final Context mContext; + + public MangaUpdatesChecker(Context context) { + mContext = context; + } + + /** + * load actual count of chapters + */ + @WorkerThread + public int fetchChaptersCount(MangaHeader manga) { + try { + final MangaProvider provider = MangaProvider.get(mContext, manga.provider); + final MangaDetails details = provider.getDetails(manga); + return details.chapters.size(); + } catch (Exception e) { + return COUNT_UNKNOWN; + } + } + + @WorkerThread + public UpdatesCheckResult fetchUpdates() { + final UpdatesCheckResult result = new UpdatesCheckResult(); + try { + final FavouritesRepository favouritesRepository = FavouritesRepository.get(mContext); + final MangaUpdatesChecker checker = new MangaUpdatesChecker(mContext); + final ArrayList favourites = favouritesRepository.query(new FavouritesSpecification()); + //noinspection ConstantConditions + for (MangaFavourite manga : favourites) { + final int total = checker.fetchChaptersCount(manga); + if (total == COUNT_UNKNOWN) { + result.fail(); + } else { + final int newChapters = total - manga.totalChapters; + if (newChapters > 0) { + final MangaUpdateInfo update = new MangaUpdateInfo( + manga.id, + manga.name, + newChapters + ); + if (favouritesRepository.putUpdateInfo(update)) { + result.add(update); + } else { + result.fail(); + } + } else if (newChapters < 0) { + //TODO + } + } + } + } catch (Exception e) { + result.error(e); + e.printStackTrace(); + } + return result; + } + + public static long getLastCheck(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getLong("mangaupdates.last_check", 0); + } + + public static void onCheckSuccess(Context context) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putLong("mangaupdates.last_check", System.currentTimeMillis()) + .apply(); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/updchecker/NotificationsChannel.java b/app/src/main/java/org/nv95/openmanga/updchecker/NotificationsChannel.java new file mode 100644 index 00000000..610c021b --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/updchecker/NotificationsChannel.java @@ -0,0 +1,74 @@ +package org.nv95.openmanga.updchecker; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.ContextCompat; + +import org.nv95.openmanga.R; +import org.nv95.openmanga.core.models.MangaUpdateInfo; +import org.nv95.openmanga.mangalist.updates.MangaUpdatesActivity; + +import java.util.List; + +class NotificationsChannel { + + private static final String CHANNEL_NAME = "manga.updates"; + + private final NotificationManager mManager; + private final Context mContext; + + NotificationsChannel(Context context) { + mContext = context; + mManager = (NotificationManager) context.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); + } + + void showUpdatesNotification(List updates) { + final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_NAME); + final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + int totalCount = 0; + for (MangaUpdateInfo o : updates) { + totalCount += o.newChapters; + style.addLine(o.newChapters + " - " + o.mangaName); + } + final String summary = mContext.getResources().getQuantityString(R.plurals.chapters_new, totalCount, totalCount); + style.setSummaryText(summary); + builder.setContentTitle(mContext.getString(R.string.new_chapters_available)); + builder.setContentText(summary); + builder.setTicker(summary); + builder.setSmallIcon(R.drawable.ic_stat_star); + builder.setStyle(style); + final int color = ContextCompat.getColor(mContext, R.color.notification_chapters); + //TODO settings + builder.setLights(color, 800, 4000); + builder.setContentIntent(PendingIntent.getActivity( + mContext, + 1, + new Intent(mContext, MangaUpdatesActivity.class), + 0 + )); + builder.setAutoCancel(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(); + } + mManager.notify(CHANNEL_NAME.hashCode(), builder.build()); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private NotificationChannel createChannel() { + final NotificationChannel channel = new NotificationChannel( + CHANNEL_NAME, + mContext.getString(R.string.checking_new_chapters), + NotificationManager.IMPORTANCE_DEFAULT + ); + channel.setLightColor(mContext.getColor(R.color.notification_chapters)); + channel.enableLights(true); + mManager.createNotificationChannel(channel); + return channel; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckJobService.java b/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckJobService.java new file mode 100644 index 00000000..1278962c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckJobService.java @@ -0,0 +1,67 @@ +package org.nv95.openmanga.updchecker; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.os.AsyncTask; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; + +import org.nv95.openmanga.common.OemBadgeHelper; +import org.nv95.openmanga.common.WeakAsyncTask; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public final class UpdatesCheckJobService extends JobService { + + private BackgroundTask mTask; + + @Override + public boolean onStartJob(JobParameters params) { + mTask = new BackgroundTask(this, params); + mTask.start(); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + if (mTask == null) { + return true; + } + if (mTask.getStatus() == AsyncTask.Status.FINISHED) { + return false; + } else { + mTask.cancel(false); + mTask = null; + return true; + } + } + + private static class BackgroundTask extends WeakAsyncTask { + + private final JobParameters mParams; + + BackgroundTask(UpdatesCheckJobService updatesCheckJobService, JobParameters params) { + super(updatesCheckJobService); + this.mParams = params; + } + + @Override + protected UpdatesCheckResult doInBackground(Void... voids) { + return new MangaUpdatesChecker(getObject()).fetchUpdates(); + } + + @Override + protected void onPostExecute(@NonNull UpdatesCheckJobService service, UpdatesCheckResult result) { + if (result.isSuccess()) { + MangaUpdatesChecker.onCheckSuccess(service); + final NotificationsChannel channel = new NotificationsChannel(service); + final int totalCount = result.getNewChaptersCount(); + new OemBadgeHelper(service).applyCount(totalCount); + if (totalCount > 0) { + channel.showUpdatesNotification(result.getUpdates()); + } + } + service.jobFinished(mParams ,!result.isSuccess()); + } + } +} diff --git a/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckResult.java b/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckResult.java new file mode 100644 index 00000000..2f7dcce7 --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckResult.java @@ -0,0 +1,52 @@ +package org.nv95.openmanga.updchecker; + +import android.support.annotation.Nullable; + +import org.nv95.openmanga.core.models.MangaUpdateInfo; + +import java.util.ArrayList; + +public class UpdatesCheckResult { + + private final ArrayList mUpdates = new ArrayList<>(); + private int mFails = 0; + @Nullable + private Exception mException = null; + + public int getNewChaptersCount() { + int total = 0; + for (MangaUpdateInfo o : mUpdates) { + total += o.newChapters; + } + return total; + } + + public int getFails() { + return mFails; + } + + public boolean isSuccess() { + return mException == null && (mFails == 0 || !mUpdates.isEmpty()); + } + + @Nullable + public Exception getError() { + return mException; + } + + public ArrayList getUpdates() { + return mUpdates; + } + + public void add(MangaUpdateInfo info) { + mUpdates.add(info); + } + + public void fail() { + mFails++; + } + + public void error(Exception e) { + mException = e; + } +} diff --git a/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckService.java b/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckService.java new file mode 100644 index 00000000..afd2a81c --- /dev/null +++ b/app/src/main/java/org/nv95/openmanga/updchecker/UpdatesCheckService.java @@ -0,0 +1,92 @@ +package org.nv95.openmanga.updchecker; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.nv95.openmanga.common.OemBadgeHelper; +import org.nv95.openmanga.common.WeakAsyncTask; +import org.nv95.openmanga.common.utils.network.NetworkUtils; + +public final class UpdatesCheckService extends Service { + + private static final String ACTION_CHECK_FORCE = "org.nv95.openmanga.ACTION_CHECK_FORCE"; + + private BackgroundTask mTask; + + @Override + public void onCreate() { + super.onCreate(); + mTask = new BackgroundTask(this); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (ACTION_CHECK_FORCE.equals(intent.getAction())) { + mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return Service.START_STICKY; + } + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + final boolean enabled = prefs.getBoolean("mangaupdates.enabled", false); + final boolean allowMetered = "0".equals(prefs.getString("mangaupdates.networktype", "0")); + if (enabled && NetworkUtils.isNetworkAvailable(this, allowMetered)) { + mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return Service.START_STICKY; + } else { + stopSelf(); + return Service.START_NOT_STICKY; + } + } + + @Override + public void onDestroy() { + if (mTask.canCancel()) { + mTask.cancel(false); + } + super.onDestroy(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private static class BackgroundTask extends WeakAsyncTask { + + BackgroundTask(UpdatesCheckService updatesCheckService) { + super(updatesCheckService); + } + + @Override + protected UpdatesCheckResult doInBackground(Void... voids) { + return new MangaUpdatesChecker(getObject()).fetchUpdates(); + } + + @Override + protected void onPostExecute(@NonNull UpdatesCheckService service, UpdatesCheckResult result) { + if (result.isSuccess()) { + MangaUpdatesChecker.onCheckSuccess(service); + final NotificationsChannel channel = new NotificationsChannel(service); + final int totalCount = result.getNewChaptersCount(); + new OemBadgeHelper(service).applyCount(totalCount); + if (totalCount > 0) { + channel.showUpdatesNotification(result.getUpdates()); + } + } + service.stopSelf(); + } + } + + public static void runForce(Context context) { + final Intent intent = new Intent(context, UpdatesCheckService.class); + intent.setAction(ACTION_CHECK_FORCE); + context.startService(intent); + } +} diff --git a/app/src/main/java/org/nv95/openmanga/utils/AnimUtils.java b/app/src/main/java/org/nv95/openmanga/utils/AnimUtils.java deleted file mode 100644 index f594c671..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/AnimUtils.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; - -/** - * Created by nv95 on 21.12.16. - */ - -public class AnimUtils { - - private static int DURATION_SHORT = 100; - - public static void init(Context context) { - DURATION_SHORT = context.getResources().getInteger(android.R.integer.config_shortAnimTime); - } - - public static void crossfade(@Nullable final View whatHide, @Nullable View whatShow) { - if (whatShow != null && whatShow.getVisibility() != View.VISIBLE) { - cancelAnimation(whatShow); - whatShow.setAlpha(0f); - whatShow.setVisibility(View.VISIBLE); - whatShow.animate() - .alpha(1f) - .setDuration(DURATION_SHORT) - .setListener(null); - } - - if (whatHide != null && whatHide.getVisibility() == View.VISIBLE) { - cancelAnimation(whatHide); - whatHide.animate() - .alpha(0f) - .setDuration(DURATION_SHORT) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - whatHide.setVisibility(View.GONE); - whatHide.setAlpha(1); - } - }); - } - - } - - public static void zoom(@Nullable final View whatHide, @Nullable View whatShow) { - if (whatShow != null && whatShow.getVisibility() != View.VISIBLE) { - cancelAnimation(whatShow); - whatShow.setScaleX(0f); - whatShow.setScaleY(0f); - whatShow.setVisibility(View.VISIBLE); - whatShow.animate() - .scaleX(1f) - .scaleY(1f) - .setDuration(DURATION_SHORT) - .setListener(null); - } - - if (whatHide != null && whatHide.getVisibility() == View.VISIBLE) { - cancelAnimation(whatHide); - whatHide.animate() - .scaleX(0f) - .scaleY(0f) - .setDuration(DURATION_SHORT) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - whatHide.setVisibility(View.GONE); - } - }); - } - - } - - public static void zooma(@Nullable final View whatHide, @Nullable View whatShow) { - if (whatShow != null && whatShow.getVisibility() != View.VISIBLE) { - cancelAnimation(whatShow); - whatShow.setScaleX(0f); - whatShow.setScaleY(0f); - whatShow.setAlpha(0f); - whatShow.setVisibility(View.VISIBLE); - whatShow.animate() - .scaleX(1f) - .scaleY(1f) - .alpha(1f) - .setDuration(DURATION_SHORT) - .setListener(null); - } - - if (whatHide != null && whatHide.getVisibility() == View.VISIBLE) { - cancelAnimation(whatHide); - whatHide.animate() - .scaleX(0f) - .scaleY(0f) - .alpha(0f) - .setDuration(DURATION_SHORT) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - whatHide.setVisibility(View.GONE); - } - }); - } - - } - - public static void noanim(@Nullable View whatHide, @Nullable View whatShow) { - if (whatShow != null && whatShow.getVisibility() != View.VISIBLE) { - cancelAnimation(whatShow); - whatShow.setVisibility(View.VISIBLE); - } - - if (whatHide != null && whatHide.getVisibility() == View.VISIBLE) { - cancelAnimation(whatHide); - whatHide.setVisibility(View.GONE); - } - - } - - private static void cancelAnimation(@NonNull View view) { - view.animate().cancel(); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/AppHelper.java b/app/src/main/java/org/nv95/openmanga/utils/AppHelper.java deleted file mode 100755 index 6b0326f1..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/AppHelper.java +++ /dev/null @@ -1,181 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.Html; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.format.DateUtils; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Locale; - -/** - * Created by nv95 on 18.12.15. - */ -public class AppHelper { - - public static String getRawString(Context context, int res) { - InputStream is = null; - try { - Resources resources = context.getResources(); - is = resources.openRawResource(res); - String myText; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int i = is.read(); - while (i != -1) { - baos.write(i); - i = is.read(); - } - myText = baos.toString(); - return myText; - } catch (IOException e) { - return e.getMessage(); - } finally { - try { - if (is != null) { - is.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static String getReadableDateTime(long milliseconds) { - DateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm", Locale.getDefault()); - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(milliseconds); - return formatter.format(calendar.getTime()); - } - - public static String getReadableDateTime(Context context, long milliseconds) { - try { - String pattern = ((SimpleDateFormat) android.text.format.DateFormat.getLongDateFormat(context)).toLocalizedPattern(); - pattern += " " + ((SimpleDateFormat) android.text.format.DateFormat.getTimeFormat(context)).toLocalizedPattern(); - DateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(milliseconds); - return formatter.format(calendar.getTime()); - } catch (Exception e) { - e.printStackTrace(); - return getReadableDateTime(milliseconds); - } - } - - public static String getReadableDateTimeRelative(long milliseconds) { - return DateUtils.getRelativeTimeSpanString(milliseconds, System.currentTimeMillis(), 0L, - DateUtils.FORMAT_ABBREV_ALL).toString(); - } - - @Nullable - public static File getFileFromUri(Context context, Uri uri) { - if ("content".equalsIgnoreCase(uri.getScheme())) { - String[] projection = {"_data"}; - Cursor cursor = null; - String res = null; - try { - cursor = context.getContentResolver().query(uri, projection, null, null, null); - int columnIndex = cursor.getColumnIndexOrThrow("_data"); - if (cursor.moveToFirst()) { - res = cursor.getString(columnIndex); - } - } catch (Exception e) { - // Eat it - } finally { - if (cursor != null) { - cursor.close(); - } - } - if (res != null) { - return new File(res); - } - } else if ("file".equalsIgnoreCase(uri.getScheme())) { - return new File(uri.getPath()); - } - return null; - } - - public static String[] getStringArray(Context context, int[] ids) { - String[] res = new String[ids.length]; - for (int i = 0; i < ids.length; i++) { - res[i] = context.getString(ids[i]); - } - return res; - } - - public static String ellipsize(String str, int len) { - if (str.length() <= len) { - return str; - } else { - return str.substring(0, len - 1) + '…'; - } - } - - public static Spanned spannedToUpperCase(@NonNull Spanned s) { - Object[] spans = s.getSpans(0, - s.length(), Object.class); - SpannableString spannableString = new SpannableString(s.toString().toUpperCase()); - - // reapply the spans to the now uppercase string - for (Object span : spans) { - spannableString.setSpan(span, - s.getSpanStart(span), - s.getSpanEnd(span), - 0); - } - - return spannableString; - } - - public static Spanned fromHtml(String html, boolean allCaps) { - Spanned spanned; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - spanned = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); - } else { - spanned = Html.fromHtml(html); - } - if (allCaps) { - spanned = spannedToUpperCase(spanned); - } - return spanned; - } - - /** - * Ignore nulls - */ - @NonNull - public static String concatStr(String... args) { - final StringBuilder builder = new StringBuilder(); - for (String o : args) { - if (o != null) { - builder.append(o); - } - } - return builder.toString(); - } - - public static String getDeviceSummary() { - return Build.MANUFACTURER + - ' ' + - Build.MODEL + - " (Android " + - Build.VERSION.RELEASE + - ")"; - } - - public static String strNotNull(String str) { - return str == null ? "" : str; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/BackupRestoreUtil.java b/app/src/main/java/org/nv95/openmanga/utils/BackupRestoreUtil.java deleted file mode 100644 index b421c0c7..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/BackupRestoreUtil.java +++ /dev/null @@ -1,372 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Environment; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.widget.Toast; - -import org.json.JSONArray; -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.ContentShareHelper; -import org.nv95.openmanga.helpers.DirRemoveHelper; -import org.nv95.openmanga.helpers.StorageHelper; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; - -/** - * Created by nv95 on 14.01.16. - */ -public class BackupRestoreUtil { - - public static final int BACKUP_IMPORT_CODE = 72; - - private final Context mContext; - - public BackupRestoreUtil(Context context) { - mContext = context; - } - - public static void showBackupDialog(final Context context) { - String[] items = new String[]{ - context.getString(R.string.action_history), - context.getString(R.string.action_favourites) - }; - final boolean[] checked = new boolean[]{true, true}; - new AlertDialog.Builder(context) - .setMultiChoiceItems(items, checked, new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - checked[which] = isChecked; - } - }) - .setTitle(R.string.backup) - .setNegativeButton(android.R.string.cancel, null) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (checked[0] || checked[1]) { - new BackupRestoreUtil(context).backup(checked); - } else { - Toast.makeText(context, R.string.nothing_selected, Toast.LENGTH_SHORT).show(); - } - } - }) - .create().show(); - } - - public static void showRestoreDialog(final Context context) { - final File[] files = getExternalBackupDir().listFiles(); - String[] items = new String[files.length]; - String name; - for (int i = 0; i < files.length; i++) { - name = files[i].getName(); - try { - items[i] = AppHelper.getReadableDateTime( - Long.valueOf(name.substring(0, name.lastIndexOf('.'))) - ); - } catch (Exception e) { - items[i] = name; - } - } - AlertDialog.Builder builder = new AlertDialog.Builder(context) - .setTitle(R.string.select_backup) - .setNegativeButton(android.R.string.cancel, null) - .setCancelable(true); - if (items.length != 0) { - builder.setItems(items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new BackupRestoreUtil(context).restore(files[which]); - } - }); - } else { - builder.setMessage(R.string.no_backups); - } - if (context instanceof Activity) { - final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("file/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - if (context.getPackageManager().resolveActivity(intent, 0) != null) { - builder.setNeutralButton(R.string.import_file, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ((Activity) context).startActivityForResult( - Intent.createChooser(intent, context.getString(R.string.import_file)), - BACKUP_IMPORT_CODE); - } - }); - } - } - builder.create().show(); - } - - public static File getExternalBackupDir() { - File dir = new File(Environment.getExternalStorageDirectory(), ".backup"); - if (!dir.exists()) { - dir.mkdirs(); - } - dir = new File(dir, "OpenManga"); - if (!dir.exists()) { - dir.mkdirs(); - } - return dir; - } - - public void backup(boolean[] what) { - new BackupTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, what[0], what[1]); - } - - public void restore(File what) { - new RestoreTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, what); - } - - private class BackupTask extends AsyncTask { - private final ProgressDialog processDialog; - - private BackupTask() { - processDialog = new ProgressDialog(mContext); - processDialog.setTitle(mContext.getString(R.string.backup)); - processDialog.setMessage(mContext.getString(R.string.preparing)); - processDialog.setIndeterminate(true); - processDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - processDialog.setCancelable(false); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - processDialog.show(); - } - - @Override - protected File doInBackground(Boolean... params) { - int errors = 0; - JSONArray jsonArray; - File dir = mContext.getExternalFilesDir("backup"); - if (dir == null) { - return null; - } - if (dir.exists()) { - new DirRemoveHelper(dir).run(); - } - dir.mkdir(); - File file; - - StorageHelper storageHelper = new StorageHelper(mContext); - //backup history - if (params[0]) { - publishProgress(R.string.action_history); - file = new File(dir, "history.json"); - jsonArray = storageHelper.extractTableData("history", null); - if (jsonArray == null || !writeToFile(file, jsonArray.toString())) { - errors++; - } - } - //backup favourites - if (params[1]) { - publishProgress(R.string.action_favourites); - file = new File(dir, "favourites.json"); - jsonArray = storageHelper.extractTableData("favourites", null); - if (jsonArray == null || !writeToFile(file, jsonArray.toString())) { - errors++; - } - } - storageHelper.close(); - publishProgress(R.string.wait); - String name = System.currentTimeMillis() + ".backup"; - ZipBuilder zipBuilder = null; - try { - file = new File(getExternalBackupDir(), name); - zipBuilder = new ZipBuilder(file); - zipBuilder.addFiles(dir.listFiles()).build(); - } catch (IOException e) { - file = null; - } finally { - if (zipBuilder != null) { - zipBuilder.close(); - } - } - new DirRemoveHelper(dir).run(); - return file; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - processDialog.setMessage(mContext.getString(values[0])); - } - - @Override - protected void onPostExecute(final File file) { - super.onPostExecute(file); - processDialog.dismiss(); - if (file != null) { - new AlertDialog.Builder(mContext) - .setMessage(R.string.backup_done) - .setTitle(R.string.backup) - .setNegativeButton(R.string.done, null) - .setPositiveButton(R.string.export_file, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new ContentShareHelper(mContext).exportFile(file); - } - }) - .create().show(); - } else { - new AlertDialog.Builder(mContext) - .setMessage(R.string.error) - .setTitle(R.string.backup) - .setPositiveButton(R.string.close, null) - .create().show(); - } - } - - private boolean writeToFile(File file, String data) { - OutputStreamWriter outputStreamWriter = null; - try { - outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file)); - outputStreamWriter.write(data); - outputStreamWriter.close(); - return true; - } catch (IOException e) { - return false; - } finally { - try { - if (outputStreamWriter != null) { - outputStreamWriter.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - private class RestoreTask extends AsyncTask { - private final ProgressDialog processDialog; - - private RestoreTask() { - processDialog = new ProgressDialog(mContext); - processDialog.setTitle(mContext.getString(R.string.restore)); - processDialog.setMessage(mContext.getString(R.string.preparing)); - processDialog.setIndeterminate(true); - processDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - processDialog.setCancelable(false); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - processDialog.show(); - } - - @Override - protected Boolean doInBackground(File... params) { - int errors = 0; - File dir = mContext.getExternalFilesDir("restore"); - if (dir == null) { - return null; - } - if (dir.exists()) { - new DirRemoveHelper(dir).run(); - } - dir.mkdir(); - File file = new File(dir, "backup.zip"); - try { - StorageUtils.copyFile(params[0], file); - } catch (IOException e) { - return false; - } - if (ZipBuilder.unzipFiles(file, dir) == null) { - new DirRemoveHelper(dir).run(); - return null; - } - StorageHelper storageHelper = new StorageHelper(mContext); - JSONArray data; - //restore history - file = new File(dir, "history.json"); - if (file.exists()) { - publishProgress(R.string.action_history); - try { - data = new JSONArray(readFromFile(file)); - if (!storageHelper.insertTableData("history", data)) { - errors++; - } - } catch (Exception e) { - errors++; - } - } - //restore favourites - file = new File(dir, "favourites.json"); - if (file.exists()) { - publishProgress(R.string.action_favourites); - try { - data = new JSONArray(readFromFile(file)); - if (!storageHelper.insertTableData("favourites", data)) { - errors++; - } - } catch (Exception e) { - errors++; - } - } - storageHelper.close(); - new DirRemoveHelper(dir).run(); - return errors == 0; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - } - - @Override - protected void onPostExecute(Boolean aBoolean) { - super.onPostExecute(aBoolean); - processDialog.dismiss(); - new AlertDialog.Builder(mContext) - .setMessage(aBoolean ? R.string.restore_done : R.string.error) - .setTitle(R.string.restore) - .setPositiveButton(R.string.close, null) - .create().show(); - } - - @Nullable - private String readFromFile(File file) { - BufferedReader bufferedReader = null; - try { - bufferedReader = new BufferedReader( - new InputStreamReader(new FileInputStream(file)) - ); - String receiveString = ""; - StringBuilder stringBuilder = new StringBuilder(); - while ((receiveString = bufferedReader.readLine()) != null) { - stringBuilder.append(receiveString); - } - return stringBuilder.toString(); - } catch (Exception e) { - return null; - } finally { - try { - if (bufferedReader != null) { - bufferedReader.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/ChangesObserver.java b/app/src/main/java/org/nv95/openmanga/utils/ChangesObserver.java deleted file mode 100644 index 832defb5..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/ChangesObserver.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016 Vasily Nikitin - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see http://www.gnu.org/licenses/. - */ - -package org.nv95.openmanga.utils; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.items.MangaInfo; - -import java.util.Vector; - -/** - * Created by nv95 on 03.01.16. - */ -public class ChangesObserver { - - private static ChangesObserver instance = new ChangesObserver(); - private final Vector mListeners = new Vector<>(); - - public static ChangesObserver getInstance() { - return instance; - } - - public void addListener(OnMangaChangesListener listener) { - mListeners.add(listener); - } - - public void removeListener(OnMangaChangesListener listener) { - mListeners.remove(listener); - } - - /** - * - * @param id -1 to update all content, otherwise manga id - * @param manga - */ - public void emitOnLocalChanged(int id, @Nullable MangaInfo manga) { - for (OnMangaChangesListener o : mListeners) { - o.onLocalChanged(id, manga); - } - } - - public void emitOnFavouritesChanged(@NonNull MangaInfo manga, int category) { - for (OnMangaChangesListener o : mListeners) { - o.onFavouritesChanged(manga, category); - } - } - - public void emitOnHistoryChanged(@NonNull MangaInfo manga) { - for (OnMangaChangesListener o : mListeners) { - o.onHistoryChanged(manga); - } - } - - public interface OnMangaChangesListener { - void onLocalChanged(int id, @Nullable MangaInfo manga); - void onFavouritesChanged(@NonNull MangaInfo manga, int category); - void onHistoryChanged(@NonNull MangaInfo manga); - } - -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/CookieParser.java b/app/src/main/java/org/nv95/openmanga/utils/CookieParser.java deleted file mode 100644 index ea73b416..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/CookieParser.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.support.annotation.Nullable; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Created by nv95 on 21.11.16. - */ - -public class CookieParser { - - private final HashMap mCookies; - - public CookieParser(List cookies) { - mCookies = new HashMap<>(); - String s; - String[] sa ,sa1; - for (String o : cookies) { - s = o.substring(0, o.indexOf(";")); - sa1 = s.split("="); - if (sa1.length == 2) { - mCookies.put(sa1[0], sa1[1]); - } - } - } - - @Nullable - public String getValue(String key) { - return mCookies.get(key); - } - - @Override - public String toString() { - StringBuilder res = new StringBuilder(); - for (Map.Entry o : mCookies.entrySet()) { - res.append(o.getKey()).append("=").append(o.getValue()).append("; "); - } - //res.delete(res.length() - 2, res.length() - 1); - return res.toString(); - } - - public String toString(String... values) { - StringBuilder res = new StringBuilder(); - for (String key : values) { - res.append(key).append("=").append(mCookies.get(key)).append("; "); - } - return res.substring(0, res.length() - 2); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/DecartUtils.java b/app/src/main/java/org/nv95/openmanga/utils/DecartUtils.java deleted file mode 100644 index e4185e84..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/DecartUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.graphics.Rect; - -/** - * Created by admin on 16.08.17. - */ - -public class DecartUtils { - - public static void scaleRect(Rect outRect, float scaleFactor) { - outRect.top *= scaleFactor; - outRect.right *= scaleFactor; - outRect.bottom *= scaleFactor; - outRect.left *= scaleFactor; - } - - public static void trimRect(Rect outRect, Rect bounds) { - if (outRect.top < bounds.top) outRect.top = bounds.top; - if (outRect.left < bounds.left) outRect.left = bounds.left; - if (outRect.right > bounds.right) outRect.right = bounds.right; - if (outRect.bottom > bounds.bottom) outRect.bottom = bounds.bottom; - } - - public static void translateRect(Rect outRect, int dX, int dY) { - outRect.top += dY; - outRect.left += dX; - outRect.bottom += dY; - outRect.right += dX; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/DeltaUpdater.java b/app/src/main/java/org/nv95/openmanga/utils/DeltaUpdater.java deleted file mode 100644 index 0f830034..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/DeltaUpdater.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.os.AsyncTask; - -import org.nv95.openmanga.MangaListLoader; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.lists.MangaList; -import org.nv95.openmanga.providers.MangaProvider; - -import java.lang.ref.WeakReference; -import java.util.Iterator; - -/** - * Created by admin on 14.07.17. - */ - -public class DeltaUpdater { - - private MangaListLoader mLoader; - - public DeltaUpdater(MangaListLoader loader) { - mLoader = loader; - } - - public void update(MangaProvider provider) { - if (provider.isMultiPage()) { - throw new RuntimeException("Provider must not be multipaged"); - } - new ContentLoadTask(mLoader).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, provider); - } - - private static class ContentLoadTask extends AsyncTask { - - private final WeakReference mLoaderRef; - - ContentLoadTask(MangaListLoader loader) { - mLoaderRef = new WeakReference<>(loader); - } - - @Override - protected MangaList doInBackground(MangaProvider... mangaProviders) { - try { - return mangaProviders[0].getList(0, 0, 0); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(MangaList newList) { - super.onPostExecute(newList); - if (newList == null) { - return; - } - MangaListLoader loader = mLoaderRef.get(); - if (loader == null) { - return; - } - MangaList oldList = loader.getList(); - for (MangaInfo newItem: newList) { - if (!oldList.contains(newItem)) { - loader.addItem(newItem); - } - } - Iterator it = oldList.iterator(); - while (it.hasNext()) { - MangaInfo oldItem = it.next(); - if (!newList.contains(oldItem)) { - loader.notifyRemoved(oldList.indexOf(oldItem.id)); - it.remove(); - } - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/DrawerHeaderImageTool.java b/app/src/main/java/org/nv95/openmanga/utils/DrawerHeaderImageTool.java deleted file mode 100644 index 5886dffa..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/DrawerHeaderImageTool.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.ThumbnailUtils; -import android.net.Uri; -import android.provider.MediaStore; -import android.support.design.widget.NavigationView; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.soundcloud.android.crop.Crop; - -import org.nv95.openmanga.R; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -import static android.app.Activity.RESULT_OK; -import static org.nv95.openmanga.R.id.imageView; -import static org.nv95.openmanga.R.id.textView; - -/** - * Created by Владимир on 17.03.2016. - */ -public class DrawerHeaderImageTool implements View.OnClickListener { - - private static final int REQUEST_IMAGE_HEADER = 434; - - private final ImageView mImageView; - private final TextView mTextView; - private final AppCompatActivity mActivity; - private final File mImageFile; - - public DrawerHeaderImageTool(AppCompatActivity activity, NavigationView navigationView) { - mActivity = activity; - View v = navigationView.getHeaderView(0); - mImageView = v.findViewById(imageView); - mTextView = v.findViewById(textView); - mImageFile = new File(activity.getExternalFilesDir("temp"), "header"); - mImageView.setOnClickListener(this); - } - - public void initDrawerImage() { - if(mImageFile.exists()) { - mImageView.setImageBitmap(BitmapFactory.decodeFile(mImageFile.getPath())); - mTextView.setVisibility(View.GONE); - } else { - mImageView.setImageResource(R.drawable.side_nav_bar); - mTextView.setVisibility(View.VISIBLE); - } - } - - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != RESULT_OK) { - return; - } - switch (requestCode) { - case REQUEST_IMAGE_HEADER: //галлерея - String file = getImageFile(mActivity, data.getData()); - if (file != null) { - beginCrop(data.getData()); - } else { - Toast.makeText(mActivity, R.string.error,Toast.LENGTH_SHORT).show(); - } - break; - case Crop.REQUEST_CROP: //crop - Bitmap source = BitmapFactory.decodeFile(mImageFile.getPath()); - Bitmap thumb = ThumbnailUtils.extractThumbnail(source, mImageView.getWidth(), mImageView.getHeight()); - source.recycle(); - //save to file - FileOutputStream out = null; - try { - out = new FileOutputStream(mImageFile); - thumb.compress(Bitmap.CompressFormat.PNG, 100, out); - } catch (Exception e) { - e.printStackTrace(); - mImageFile.delete(); - } finally { - thumb.recycle(); - try { - if (out != null) { - out.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - initDrawerImage(); - break; - } - } - - @Override - public void onClick(View v) { - requestImageSelection(mActivity, REQUEST_IMAGE_HEADER); - } - - public static String getImageFile(Context context, Uri uri) { - String[] filePathColumn = {MediaStore.Images.Media.DATA}; - String filePath = null; - Cursor cursor = context.getContentResolver().query( - uri, filePathColumn, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - int columnIndex = cursor.getColumnIndex(filePathColumn[0]); - filePath = cursor.getString(columnIndex); - cursor.close(); - } - return filePath; - } - - private void beginCrop(Uri source) { - Uri destination = Uri.fromFile(mImageFile); - Crop.of(source, destination) - .withAspect(mImageView.getWidth(), mImageView.getHeight()) - .start(mActivity); - } - - public static void requestImageSelection(Activity activity, int requestCode) { - Intent pickerIntent = new Intent(Intent.ACTION_PICK); - pickerIntent.setType("image/*"); - pickerIntent = Intent.createChooser(pickerIntent, activity.getString(R.string.select_image)); - activity.startActivityForResult(pickerIntent, requestCode); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/ExImageDownloader.java b/app/src/main/java/org/nv95/openmanga/utils/ExImageDownloader.java deleted file mode 100644 index 0f653d5f..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/ExImageDownloader.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.Context; -import android.net.Uri; - -import com.nostra13.universalimageloader.core.download.BaseImageDownloader; - -import org.nv95.openmanga.providers.staff.MangaProviderManager; - -import java.io.IOException; -import java.net.HttpURLConnection; - -import info.guardianproject.netcipher.NetCipher; - -/** - * Created by nv95 on 21.11.16. - */ - -class ExImageDownloader extends BaseImageDownloader { - - ExImageDownloader(Context context) { - super(context); - } - - @Override - protected HttpURLConnection createConnection(String url, Object extra) throws IOException { - String nurl = url.startsWith("https:") ? "http" + url.substring(5) : url; - nurl = Uri.encode(nurl, ALLOWED_URI_CHARS); - HttpURLConnection conn = NetCipher.getHttpURLConnection(nurl); - conn.setConnectTimeout(connectTimeout); - conn.setReadTimeout(readTimeout); - MangaProviderManager.prepareConnection(conn); - return conn; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/FileLogger.java b/app/src/main/java/org/nv95/openmanga/utils/FileLogger.java deleted file mode 100755 index 96b44b90..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/FileLogger.java +++ /dev/null @@ -1,170 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Build; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.widget.Toast; - -import org.nv95.openmanga.BuildConfig; -import org.nv95.openmanga.R; -import org.nv95.openmanga.components.reader.FileConverter; -import org.nv95.openmanga.providers.staff.MangaProviderManager; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; - -/** - * Created by nv95 on 16.10.15. - */ -public class FileLogger implements Thread.UncaughtExceptionHandler { - - private static FileLogger instance; - private Thread.UncaughtExceptionHandler mOldHandler; - private final File mLogFile; - - private FileLogger(Context context) { - mLogFile = getLogFile(context); - } - - public static FileLogger getInstance() { - return instance; - } - - public static void init(Context context) { - instance = new FileLogger(context); - instance.mOldHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(instance); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - final int logVersion = prefs.getInt("_log_version", 0); - if (logVersion < BuildConfig.VERSION_CODE) { - prefs.edit().putInt("_log_version", BuildConfig.VERSION_CODE).apply(); - instance.upgrade(); - } - } - - private void upgrade() { - FileOutputStream ostream = null; - try { - mLogFile.delete(); - ostream = new FileOutputStream(mLogFile, true); - ostream.write(("Init logger: " + AppHelper.getReadableDateTime(System.currentTimeMillis()) - + "\nApp version: " + BuildConfig.VERSION_CODE - + " (" + BuildConfig.VERSION_NAME - + ")\n\nDevice info:\n" + Build.FINGERPRINT - + "\nAndroid: " + Build.VERSION.RELEASE + " (API v" + Build.VERSION.SDK_INT + ")\n\n").getBytes()); - ostream.flush(); - } catch (Exception e) { - //ну и фиг с ним - } finally { - try { - if (ostream != null) { - ostream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static void sendLog(final Context context) { - new AlertDialog.Builder(context).setTitle(R.string.bug_report) - .setMessage(R.string.bug_report_message) - .setPositiveButton(R.string.send, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - File file = getLogFile(context); - if (!file.exists()) { - Toast.makeText(context, R.string.log_not_found, Toast.LENGTH_SHORT).show(); - return; - } - Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); - emailIntent.setType("message/rfc822"); - emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] - {"nvasya95@gmail.com"}); - emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, - "Error report for OpenManga"); - emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + file.getAbsolutePath())); - context.startActivity(Intent.createChooser(emailIntent, context.getString(R.string.bug_report))); - } - }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }).create().show(); - } - - public synchronized void report(String msg) { - FileOutputStream ostream = null; - try { - ostream = new FileOutputStream(mLogFile, true); - msg += "\n **************** \n"; - ostream.write(msg.getBytes()); - ostream.flush(); - ostream.close(); - //Toast.makeText(context, R.string.exception_logged, Toast.LENGTH_SHORT).show(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if (ostream != null) { - ostream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public void report(String tag, @Nullable Exception e) { - if (e != null) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - report(tag + "\n" + e.getMessage() + "\n\tStack trace:\n" + sw.toString()); - } else { - report("Null exception"); - } - } - - @Deprecated - public void report(Exception e) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - report(e.getMessage() + "\n\tStack trace:\n" + sw.toString()); - } - - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - public String getFailMessage(Context context, @Nullable Exception e) { - if ((e == null || e instanceof IOException) && !MangaProviderManager.checkConnection(context)) { - return context.getString(R.string.no_network_connection); - } else if (e instanceof FileNotFoundException) { - return context.getString(R.string.file_not_found); - } else if (e instanceof FileConverter.ConvertException) { - return context.getString(R.string.image_decode_error); - } else { - report("IMGLOAD", e); - return context.getString(R.string.image_loading_error); - } - } - - @Override - public void uncaughtException(Thread thread, Throwable ex) { - report("!CRASH\n" + ex.getMessage() + "\n\n" + ex.getCause() + "\n"); - if (mOldHandler != null) - mOldHandler.uncaughtException(thread, ex); - } - - private static File getLogFile(Context context) { - return new File(context.getExternalFilesDir("debug"), "log.txt"); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/ImageUtils.java b/app/src/main/java/org/nv95/openmanga/utils/ImageUtils.java deleted file mode 100644 index 33a4655e..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/ImageUtils.java +++ /dev/null @@ -1,151 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.media.ThumbnailUtils; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.text.TextUtils; -import android.widget.ImageView; - -import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache; -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; -import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; -import com.nostra13.universalimageloader.core.imageaware.ImageViewAware; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.components.TransitionDisplayer; -import org.nv95.openmanga.items.ThumbSize; - -import java.io.File; - -/** - * Created by admin on 02.09.16. - */ - -public class ImageUtils { - - public static final int CACHE_MAX_MB = 1024; - public static final int CACHE_MIN_MB = 20; - - private static DisplayImageOptions mOptionsThumb = null; - private static DisplayImageOptions mOptionsUpdate = null; - - public static void init(Context context) { - if (!ImageLoader.getInstance().isInited()) { - int cacheMb = 100; - try { - cacheMb = PreferenceManager.getDefaultSharedPreferences(context) - .getInt("cache_max", 100); - if (cacheMb < CACHE_MIN_MB) { - cacheMb = CACHE_MIN_MB; - } else if (cacheMb > CACHE_MAX_MB) { - cacheMb = CACHE_MAX_MB; - } - } catch (NumberFormatException e) { - FileLogger.getInstance().report("PREF", e); - } - ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) - .defaultDisplayImageOptions(getImageLoaderOptionsBuilder().build()) - .diskCacheSize(cacheMb * 1024 * 1024) //100 Mb - .diskCacheFileCount(100) - .imageDownloader(new ExImageDownloader(context)) - .memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // 2 Mb - .build(); - - ImageLoader.getInstance().init(config); - } - if (mOptionsThumb == null) { - Drawable holder = ContextCompat.getDrawable(context, R.drawable.placeholder); - mOptionsThumb = getImageLoaderOptionsBuilder() - .showImageOnFail(holder) - .showImageForEmptyUri(holder) - .showImageOnLoading(holder) - .build(); - } - - if (mOptionsUpdate == null) { - mOptionsUpdate = getImageLoaderOptionsBuilder() - .displayer(new TransitionDisplayer()) - .build(); - } - } - - @Nullable - public static Bitmap getCachedImage(String url) { - try { - Bitmap b = ImageLoader.getInstance().getMemoryCache().get(url); - if (b == null) { - File f = ImageLoader.getInstance().getDiskCache().get(url); - if (f != null) { - b = BitmapFactory.decodeFile(f.getPath()); - } - } - return b; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - private static DisplayImageOptions.Builder getImageLoaderOptionsBuilder() { - return new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .resetViewBeforeLoading(false) - .displayer(new FadeInBitmapDisplayer(200, true, true, false)); - } - - private static String fixUrl(String url) { - return (!TextUtils.isEmpty(url) && url.charAt(0) == '/') ? "file://" + url : url; - } - - public static void setThumbnail(@NonNull ImageView imageView, String url, @Nullable ThumbSize size) { - ImageLoader.getInstance().displayImage( - fixUrl(url), - new ImageViewAware(imageView), - mOptionsThumb, - size != null && imageView.getMeasuredWidth() == 0 ? size.toImageSize() : null, - null, - null - ); - } - - public static void setThumbnail(@NonNull ImageView imageView, String url) { - setThumbnail(imageView, url, null); - } - - public static void setImage(@NonNull ImageView imageView, String url) { - ImageLoader.getInstance().displayImage( - fixUrl(url), - imageView - ); - } - - public static void updateImage(@NonNull ImageView imageView, String url) { - ImageLoader.getInstance().displayImage( - fixUrl(url), - imageView, - mOptionsUpdate - ); - } - - @Nullable - public static Bitmap getThumbnail(String path, int width, int height) { - Bitmap bitmap = getCachedImage(path); - if (bitmap == null && path.startsWith("/")) { - bitmap = BitmapFactory.decodeFile(path); - } - if (bitmap != null) { - bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); - } - return bitmap; - } - -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/InternalLinkMovement.java b/app/src/main/java/org/nv95/openmanga/utils/InternalLinkMovement.java deleted file mode 100644 index 550e8aaf..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/InternalLinkMovement.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.provider.Settings; -import android.support.annotation.Nullable; -import android.text.Layout; -import android.text.Selection; -import android.text.Spannable; -import android.text.method.LinkMovementMethod; -import android.text.style.URLSpan; -import android.view.MotionEvent; -import android.widget.TextView; - -import org.nv95.openmanga.R; - -/** - * Created by nv95 on 12.01.16. - */ -public class InternalLinkMovement extends LinkMovementMethod { - - @Nullable - private final OnLinkClickListener mLinkClickListener; - - public InternalLinkMovement(@Nullable OnLinkClickListener linkClickListener) { - this.mLinkClickListener = linkClickListener; - } - - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, - MotionEvent event) { - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP || - action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - - URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); - - if (link.length != 0) { - if (action == MotionEvent.ACTION_UP) { - Selection.removeSelection(buffer); - processClick(widget.getContext(), link[0].getURL(), widget); - } else { - Selection.setSelection(buffer, - buffer.getSpanStart(link[0]), - buffer.getSpanEnd(link[0])); - } - - return true; - } else { - Selection.removeSelection(buffer); - } - } - - return super.onTouchEvent(widget, buffer, event); - } - - private void processClick(Context context, String url, TextView textView) { - String[] parts = url.split(":"); - if (parts.length != 2) { - FileLogger.getInstance().report("Invalid link: " + url); - return; - } - switch (parts[0]) { - case "activity": - String packageName = context.getPackageName(); - context.startActivity(new Intent().setComponent(new ComponentName( - packageName, packageName + "." + parts[1] - ))); - break; - case "http": - case "https": - context.startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(url))); - break; - case "mailto": - Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); - emailIntent.setType("message/rfc822"); - emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] - {parts[1]}); - context.startActivity(Intent.createChooser(emailIntent, context.getString(R.string.send_mail))); - break; - case "settings": - switch (parts[1]) { - case "wifi": - context.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)); - break; - case "network": - context.startActivity(new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)); - break; - } - break; - default: - if (mLinkClickListener != null) { - mLinkClickListener.onLinkClicked(textView, parts[0], parts[1]); - } - } - } - - public interface OnLinkClickListener { - void onLinkClicked(TextView view, String scheme, String url); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/LayoutUtils.java b/app/src/main/java/org/nv95/openmanga/utils/LayoutUtils.java deleted file mode 100644 index f5e70bdd..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/LayoutUtils.java +++ /dev/null @@ -1,295 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.animation.ValueAnimator; -import android.app.Activity; -import android.app.ActivityManager; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; -import android.preference.PreferenceManager; -import android.support.annotation.AttrRes; -import android.support.annotation.ColorRes; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v7.widget.OrientationHelper; -import android.support.v7.widget.RecyclerView; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.Display; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.inputmethod.InputMethodManager; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.items.ThumbSize; - -/** - * Created by nv95 on 26.01.16. - */ -public class LayoutUtils { - - private static final int[] THEMES = new int[]{ - R.style.AppTheme_Default, - R.style.AppTheme_Classic, - R.style.AppTheme_Grey, - R.style.AppTheme_Teal, - R.style.AppTheme_Blue, - R.style.AppTheme_Purple, - R.style.AppTheme_Ambiance, - R.style.AppTheme_Indigo, - R.style.AppThemeDark_Classic, - R.style.AppThemeDark_Blue, - R.style.AppThemeDark_Teal, - R.style.AppThemeDark_Miku, - R.style.AppThemeBlack_Grey, - R.style.AppThemeBlack_Red, - R.style.AppThemeBlack_Black - }; - - public static boolean isTablet(Context context) { - return context.getResources().getConfiguration().isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); - } - - public static boolean isLandscape(Context context) { - return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; - } - - public static boolean isTabletLandscape(Context context) { - return isTablet(context) && isLandscape(context); - } - - public static int getScreenSize(Context context) { - return context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; - } - - public static Float[] getScreenSizeDp(Activity activity) { - Display display = activity.getWindowManager().getDefaultDisplay(); - DisplayMetrics outMetrics = new DisplayMetrics(); - display.getMetrics(outMetrics); - - float density = activity.getResources().getDisplayMetrics().density; - float dpHeight = outMetrics.heightPixels / density; - float dpWidth = outMetrics.widthPixels / density; - return new Float[]{dpHeight, dpWidth}; - } - - public static int DpToPx(Resources res, float dp) { - float density = res.getDisplayMetrics().density; - return (int) (dp * density); - } - - public static int getOptimalColumnsCount(Resources resources, ThumbSize thumbSize) { - float width = resources.getDisplayMetrics().widthPixels; - int count = Math.round(width / (thumbSize.getWidth() + DpToPx(resources, 8))); - return count == 0 ? 1 : count; - } - - public static void setAllImagesColor(ViewGroup container, @ColorRes int colorId) { - int color = ContextCompat.getColor(container.getContext(), colorId); - View o; - for (int i = container.getChildCount() - 1;i >= 0;i--) { - o = container.getChildAt(i); - if (o instanceof ImageView) { - ((ImageView) o).setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - } else if (o instanceof TextView) { - for (Drawable d : ((TextView)o).getCompoundDrawables()) { - if (d != null) { - DrawableCompat.setTint(d, color); - } - } - } else if (o instanceof ViewGroup) { - setAllImagesColor((ViewGroup) o, colorId); - } - } - } - - public static ColorStateList getColorStateList(Context context, int id) { - TypedArray a = context.getTheme().obtainStyledAttributes(new int[] {id}); - int attributeResourceId = a.getResourceId(0, 0); - return ContextCompat.getColorStateList(context, attributeResourceId); - } - - public static Drawable[] getThemedIcons(Context context, int... ids) { - boolean dark = isAppThemeDark(context); - PorterDuffColorFilter cf = dark ? - new PorterDuffColorFilter(ContextCompat.getColor(context, R.color.white_overlay_85), PorterDuff.Mode.SRC_ATOP) - : null; - Drawable[] ds = new Drawable[ids.length]; - for (int i=0;i fromIndex ? 1 : -1; - View partiallyVisible = null; - for (int i = fromIndex; i != toIndex; i += next) { - final View child = layoutManager.getChildAt(i); - final int childStart = helper.getDecoratedStart(child); - final int childEnd = helper.getDecoratedEnd(child); - if (childStart < end && childEnd > start) { - if (completelyVisible) { - if (childStart >= start && childEnd <= end) { - return child; - } else if (acceptPartiallyVisible && partiallyVisible == null) { - partiallyVisible = child; - } - } else { - return child; - } - } - } - return partiallyVisible; - } - - public static void showSoftKeyboard(@NonNull View view) { - view.requestFocus(); - InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(view, 0); - } - - public static void centeredToast(Context context, @StringRes int message) { - Toast t = Toast.makeText(context, message, Toast.LENGTH_SHORT); - t.setGravity(Gravity.CENTER, 0, 0); - t.show(); - } - - public static void hideSoftKeyboard(@NonNull View view) { - InputMethodManager imm = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - - public static int getAppTheme(Context context) { - try { - return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context) - .getString("theme", "0")); - } catch (Exception e) { - return 0; - } - } - - public static boolean isAppThemeDark(Context context) { - return getAppTheme(context) > 7; - } - - /** - * https://stackoverflow.com/questions/18635135/android-shortcut-bitmap-launcher-icon-size/19003905#19003905 - */ - public static int getLauncherIconSize(Context context) { - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - int size2 = activityManager.getLauncherLargeIconSize(); - int size1 = (int) context.getResources().getDimension(android.R.dimen.app_icon_size); - return size2 > size1 ? size2 : size1; - } - - public static int getAppThemeRes(Context context) { - return THEMES[getAppTheme(context)]; - } - - public static int getAppThemeRes(int index) { - return THEMES[index]; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/MangaStore.java b/app/src/main/java/org/nv95/openmanga/utils/MangaStore.java deleted file mode 100644 index 9aa72fae..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/MangaStore.java +++ /dev/null @@ -1,358 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.preference.PreferenceManager; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; - -import org.nv95.openmanga.helpers.DirRemoveHelper; -import org.nv95.openmanga.helpers.SpeedMeasureHelper; -import org.nv95.openmanga.helpers.StorageHelper; -import org.nv95.openmanga.items.DownloadInfo; -import org.nv95.openmanga.items.MangaChapter; -import org.nv95.openmanga.items.MangaPage; -import org.nv95.openmanga.items.SimpleDownload; - -import java.io.File; -import java.util.Date; -import java.util.concurrent.CopyOnWriteArraySet; - -/** - * Created by nv95 on 04.06.16. - * Use this instead of #StorageHelper only for saved manga - */ - -public class MangaStore { - - public static final String TABLE_MANGAS = "mangas"; - public static final String TABLE_CHAPTERS = "chapters"; - public static final String TABLE_PAGES = "pages"; - - private final DatabaseHelper mDatabaseHelper; - private final Context mContext; - - public MangaStore(Context context) { - mContext = context; - mDatabaseHelper = new DatabaseHelper(mContext); - } - - /** - * Add manga to database, create dir for it, download preview - * @param manga - what save to db - * @return manga id - */ - @WorkerThread - public int pushManga(DownloadInfo manga) { - int id = 0; - try { - SQLiteDatabase database = mDatabaseHelper.getWritableDatabase(); - final ContentValues cv = new ContentValues(); - id = manga.path.hashCode(); - cv.put("id", id); - cv.put("name", manga.name); - cv.put("subtitle", manga.subtitle); - cv.put("summary", manga.genres); - File dest = getMangaDir(mContext, database, id); - cv.put("dir", dest.getPath()); - new SimpleDownload(manga.preview, new File(dest, "cover")).run(); - cv.put("description", manga.description); - cv.put("timestamp", new Date().getTime()); - cv.put("provider", manga.provider.getName()); - cv.put("source", manga.path); - cv.put("rating", manga.rating); - if (database.update(TABLE_MANGAS,cv, "id=" + id, null) == 0) { - database.insert(TABLE_MANGAS, null, cv); - } - } catch (Exception e) { - FileLogger.getInstance().report("STORE", e); - } - return id; - } - - /** - * Add chapter to database and nothing more - * @param chapter what to add - * @param mangaId id of manga - * @return id of chapter - */ - @WorkerThread - public int pushChapter(MangaChapter chapter, int mangaId) { - int id = chapter.readLink.hashCode(); - try { - final ContentValues cv = new ContentValues(); - cv.put("id", id); - cv.put("mangaid", mangaId); - cv.put("name", chapter.name); - SQLiteDatabase database = mDatabaseHelper.getWritableDatabase(); - cv.put("number", chapter.number == -1 ? - StorageHelper.getRowCount(database, TABLE_CHAPTERS, "mangaid=" + mangaId) - : chapter.number); - if (database.update(TABLE_CHAPTERS,cv, "id=" + id, null) == 0) { - database.insert(TABLE_CHAPTERS, null, cv); - } - } catch (Exception e) { - FileLogger.getInstance().report("STORE", e); - id = 0; - } - return id; - } - - /** - * Download page, save it to manga dir and add to database - * @param page page to save - * @param mangaId id of manga - * @param chapterId id of chapter - * @return page id; - */ - @WorkerThread - public int pushPage(MangaPage page, int mangaId, int chapterId, @Nullable SpeedMeasureHelper speedMeasureHelper) { - int id = page.path.hashCode(); - try { - ContentValues cv = new ContentValues(); - cv.put("id", id); - cv.put("chapterid", chapterId); - cv.put("mangaid", mangaId); - File dest = new File(getMangaDir(mContext, mangaId), chapterId + "_" + id); - SimpleDownload sd = new SimpleDownload(page.path, dest); - sd.setSpeedMeasureHelper(speedMeasureHelper); - sd.run(); - if (!sd.isSuccess()) { - return 0; - } - cv.put("file", dest.getName()); - SQLiteDatabase database = mDatabaseHelper.getWritableDatabase(); - cv.put("number", StorageHelper.getRowCount(database, TABLE_PAGES, "chapterid=" + chapterId)); - if (database.update(TABLE_PAGES,cv, "id=" + id, null) == 0) { - database.insert(TABLE_PAGES, null, cv); - } - } catch (Exception e) { - FileLogger.getInstance().report("STORE", e); - id = 0; - } - return id; - } - - /** - * Delete mangas from database and files - * @param ids array of manga's id - * @return #true if no errors - */ - @MainThread - public boolean dropMangas(long[] ids) { - SQLiteDatabase database = null; - boolean result = true; - Cursor cursor = null; - int id; - final File[] dirs = new File[ids.length]; - try { - database = mDatabaseHelper.getWritableDatabase(); - database.beginTransaction(); - for (int i = 0;i < ids.length;i++) { - id = (int) ids[i]; - cursor = database.query(TABLE_MANGAS, new String[]{"dir"}, "id=?", new String[]{String.valueOf(id)}, null, null, null); - if (cursor.moveToFirst()) { - dirs[i] = new File(cursor.getString(0)); - } - cursor.close(); - cursor = null; - database.delete(TABLE_PAGES, "mangaid=?", new String[]{String.valueOf(id)}); - database.delete(TABLE_CHAPTERS, "mangaid=?", new String[]{String.valueOf(id)}); - database.delete(TABLE_MANGAS, "id=?", new String[]{String.valueOf(id)}); - } - database.setTransactionSuccessful(); - new DirRemoveHelper(dirs).runAsync(); - } catch (Exception e) { - FileLogger.getInstance().report("STORE", e); - result = false; - } finally { - if (database != null) { - database.endTransaction(); - } - if (cursor != null) { - cursor.close(); - } - } - return result; - } - - @WorkerThread - public boolean moveManga(long id, String destDir) { - SQLiteDatabase database = null; - boolean result = true; - Cursor cursor = null; - ContentValues cv = new ContentValues(); - cv.put("dir", destDir + File.separatorChar + String.valueOf(id)); - File dir; - try { - database = mDatabaseHelper.getWritableDatabase(); - database.beginTransaction(); - cursor = database.query(TABLE_MANGAS, new String[]{"dir"}, "id=?", new String[]{String.valueOf(id)}, null, null, null); - cursor.moveToFirst(); - dir = new File(cursor.getString(0)); - StorageUtils.moveDir(dir, destDir); - cursor.close(); - cursor = null; - database.update(TABLE_MANGAS, cv, "id=?", new String[]{String.valueOf(id)}); - database.setTransactionSuccessful(); - } catch (Exception e) { - FileLogger.getInstance().report("STORE", e); - result = false; - } finally { - if (database != null) { - database.endTransaction(); - } - if (cursor != null) { - cursor.close(); - } - } - return result; - } - - @MainThread - public boolean dropChapters(int mangaId, long[] ids) { - SQLiteDatabase database = null; - boolean result = true; - try { - database = mDatabaseHelper.getWritableDatabase(); - database.beginTransaction(); - for (long id : ids) { - database.delete(TABLE_PAGES, "chapterid=? AND mangaid=?", new String[]{String.valueOf(id), String.valueOf(mangaId)}); - database.delete(TABLE_CHAPTERS, "id=?", new String[]{String.valueOf(id)}); - new DirRemoveHelper(getMangaDir(mContext, database, mangaId), id + "_[-\\w]*").runAsync(); - } - database.setTransactionSuccessful(); - } catch (Exception e) { - FileLogger.getInstance().report("STORE", e); - result = false; - } finally { - if (database != null) { - database.endTransaction(); - } - } - return result; - } - - @WorkerThread - public boolean dropChapter(int mangaId, long id) { - SQLiteDatabase database = null; - boolean result = true; - try { - database = mDatabaseHelper.getWritableDatabase(); - database.beginTransaction(); - database.delete(TABLE_PAGES, "chapterid=? AND mangaid=?", new String[]{String.valueOf(id), String.valueOf(mangaId)}); - database.delete(TABLE_CHAPTERS, "id=?", new String[]{String.valueOf(id)}); - new DirRemoveHelper(getMangaDir(mContext, database, mangaId), id + "_[-\\w]*").run(); - database.setTransactionSuccessful(); - } catch (Exception e) { - FileLogger.getInstance().report("STORE", e); - result = false; - } finally { - if (database != null) { - database.endTransaction(); - } - } - return result; - } - - public SQLiteDatabase getDatabase(boolean writable) { - return writable ? mDatabaseHelper.getWritableDatabase() : mDatabaseHelper.getReadableDatabase(); - } - - public static File getMangasDir(Context context) { - final String dir = PreferenceManager.getDefaultSharedPreferences(context).getString("mangadir", ""); - final File res = dir.length() == 0 ? context.getExternalFilesDir("saved") : new File(dir); - assert res != null; - if (!res.exists()) { - //noinspection ResultOfMethodCallIgnored - res.mkdirs(); - } - return res; - } - - public File getMangaDir(Context context, int id) { - return getMangaDir(context, getDatabase(false), id); - } - - public static File getMangaDir(Context context, SQLiteDatabase database, int id) { - File res = null; - Cursor c = database.query(TABLE_MANGAS, new String[]{"dir"}, "id=?", new String[]{String.valueOf(id)}, null, null, null); - if (c.moveToFirst()) { - res = new File(c.getString(0)); - } - c.close(); - if (res != null && res.exists()) { - return res; - } - res = new File(getMangasDir(context), String.valueOf(id)); - if (!res.exists()) { - //noinspection ResultOfMethodCallIgnored - res.mkdirs(); - } - return res; - } - - @Override - protected void finalize() throws Throwable { - mDatabaseHelper.close(); - super.finalize(); - } - - private static class DatabaseHelper extends SQLiteOpenHelper { - - private static final int DB_VERSION = 3; - - DatabaseHelper(Context context) { - super(context, "mangastore", null, DB_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_MANGAS + " (" - + "id INTEGER PRIMARY KEY," - + "name TEXT," - + "subtitle TEXT," - + "summary TEXT," - + "description TEXT," - + "dir TEXT," //каталог с файлами - + "timestamp INTEGER," - + "source TEXT," //link to source manga - + "provider TEXT," //source provider - + "rating INTEGER DEFAULT 0" - + ");"); - - db.execSQL("CREATE TABLE " + TABLE_CHAPTERS + " (" - + "id INTEGER PRIMARY KEY," - + "mangaid INTEGER," - + "name TEXT," - + "number INTEGER" - + ");"); - - db.execSQL("CREATE TABLE " + TABLE_PAGES + " (" - + "id INTEGER PRIMARY KEY," - + "chapterid INTEGER," - + "mangaid INTEGER," - + "file TEXT," //name of file, without path - + "number INTEGER" //use for true order - + ");"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - CopyOnWriteArraySet tables = StorageHelper.getColumsNames(db, TABLE_MANGAS); - if (!tables.contains("source")) { - db.execSQL("ALTER TABLE " + TABLE_MANGAS + " ADD COLUMN source TEXT"); - } - if (!tables.contains("provider")) { - db.execSQL("ALTER TABLE " + TABLE_MANGAS + " ADD COLUMN provider TEXT"); - } - if (!tables.contains("rating")) { - db.execSQL("ALTER TABLE " + TABLE_MANGAS + " ADD COLUMN rating INTEGER DEFAULT 0"); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/NetworkStateListener.java b/app/src/main/java/org/nv95/openmanga/utils/NetworkStateListener.java deleted file mode 100644 index 657a33f5..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/NetworkStateListener.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; - -/** - * Created by admin on 23.07.17. - */ - -public class NetworkStateListener extends BroadcastReceiver { - - private final OnNetworkStateListener mListener; - - public NetworkStateListener(OnNetworkStateListener listener) { - mListener = listener; - } - - @Override - public void onReceive(Context context, Intent intent) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - mListener.onNetworkStatusChanged(activeNetwork != null && activeNetwork.isConnected()); - } - - public interface OnNetworkStateListener { - void onNetworkStatusChanged(boolean isConnected); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/NetworkUtils.java b/app/src/main/java/org/nv95/openmanga/utils/NetworkUtils.java deleted file mode 100644 index d180bd44..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/NetworkUtils.java +++ /dev/null @@ -1,245 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import org.json.JSONException; -import org.json.JSONObject; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.nv95.openmanga.items.RESTResponse; - -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URLEncoder; - -import javax.net.ssl.HttpsURLConnection; - -import info.guardianproject.netcipher.NetCipher; -import info.guardianproject.netcipher.proxy.OrbotHelper; - -/** - * Created by nv95 on 29.11.16. - */ - -public class NetworkUtils { - - public static final String HTTP_GET = "GET"; - public static final String HTTP_POST = "POST"; - public static final String HTTP_PUT = "PUT"; - public static final String HTTP_DELETE = "DELETE"; - - public static boolean setUseTor(Context context, boolean enabled) { - boolean isTor = NetCipher.getProxy() == NetCipher.ORBOT_HTTP_PROXY; - if (isTor == enabled) { - return isTor; - } - if (enabled) { - if (OrbotHelper.get(context).init()) { - NetCipher.useTor(); - return true; - } else { - return false; - } - } else { - NetCipher.clearProxy(); - return false; - } - } - - public static Document httpGet(@NonNull String url, @Nullable String cookie) throws IOException { - InputStream is = null; - try { - HttpURLConnection con = NetCipher.getHttpURLConnection(url); - if (con instanceof HttpsURLConnection) { - ((HttpsURLConnection) con).setSSLSocketFactory(NoSSLv3SocketFactory.getInstance()); - } - //con.setDoOutput(true); - if (!TextUtils.isEmpty(cookie)) { - con.setRequestProperty("Cookie", cookie); - } - con.setConnectTimeout(15000); - is = con.getInputStream(); - return Jsoup.parse(is, con.getContentEncoding(), url); - } finally { - if (is != null) { - is.close(); - } - } - } - - public static Document httpPost(@NonNull String url, @Nullable String cookie, @Nullable String[] data) throws IOException { - InputStream is = null; - try { - HttpURLConnection con = NetCipher.getHttpURLConnection(url); - if (con instanceof HttpsURLConnection) { - ((HttpsURLConnection) con).setSSLSocketFactory(NoSSLv3SocketFactory.getInstance()); - } - con.setConnectTimeout(15000); - con.setRequestMethod("POST"); - if (!TextUtils.isEmpty(cookie)) { - con.setRequestProperty("Cookie", cookie); - } - if (data != null) { - con.setDoOutput(true); - DataOutputStream out = new DataOutputStream(con.getOutputStream()); - - out.writeBytes(makeQuery(data)); - out.flush(); - out.close(); - } - is = con.getInputStream(); - return Jsoup.parse(is, con.getContentEncoding(), url); - } finally { - if (is != null) { - is.close(); - } - } - } - - @NonNull - public static String getRaw(@NonNull String url, @Nullable String cookie) throws IOException { - BufferedReader reader = null; - try { - HttpURLConnection con = NetCipher.getHttpURLConnection(url); - if (con instanceof HttpsURLConnection) { - ((HttpsURLConnection) con).setSSLSocketFactory(NoSSLv3SocketFactory.getInstance()); - } - if (!TextUtils.isEmpty(cookie)) { - con.setRequestProperty("Cookie", cookie); - } - con.setConnectTimeout(15000); - reader = new BufferedReader(new InputStreamReader(con.getInputStream())); - StringBuilder out = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - out.append(line); - } - return out.toString(); - } finally { - if (reader != null) { - reader.close(); - } - } - } - - public static JSONObject getJsonObject(@NonNull String url) throws IOException, JSONException { - return new JSONObject(getRaw(url, null)); - } - - @Nullable - public static CookieParser authorize(String url, String... data) { - DataOutputStream out = null; - try { - HttpURLConnection con = NetCipher.getHttpURLConnection(url); - if (con instanceof HttpsURLConnection) { - ((HttpsURLConnection) con).setSSLSocketFactory(NoSSLv3SocketFactory.getInstance()); - } - con.setConnectTimeout(15000); - con.setRequestMethod("POST"); - con.setDoOutput(true); - con.setInstanceFollowRedirects(true); - out = new DataOutputStream(con.getOutputStream()); - out.writeBytes(makeQuery(data)); - out.flush(); - con.connect(); - if (con.getResponseCode() == HttpURLConnection.HTTP_OK) { - return new CookieParser(con.getHeaderFields().get("Set-Cookie")); - } else { - return null; - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - try { - if (out != null) { - out.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static RESTResponse restQuery(String url, @Nullable String token, String method, String... data) { - BufferedReader reader = null; - try { - HttpURLConnection con = NetCipher.getHttpURLConnection( - HTTP_GET.equals(method) ? url + "?" + makeQuery(data) : url - ); - if (con instanceof HttpsURLConnection) { - ((HttpsURLConnection) con).setSSLSocketFactory(NoSSLv3SocketFactory.getInstance()); - } - if (!TextUtils.isEmpty(token)) { - con.setRequestProperty("X-AuthToken", token); - } - con.setConnectTimeout(15000); - con.setRequestMethod(method); - if (!HTTP_GET.equals(method)) { - con.setDoOutput(true); - DataOutputStream out = new DataOutputStream(con.getOutputStream()); - out.writeBytes(NetworkUtils.makeQuery(data)); - out.flush(); - out.close(); - } - int respCode = con.getResponseCode(); - reader = new BufferedReader(new InputStreamReader(isOk(respCode) ? con.getInputStream() : con.getErrorStream())); - StringBuilder out = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - out.append(line); - } - return new RESTResponse(new JSONObject(out.toString()), respCode); - } catch (Exception e) { - e.printStackTrace(); - return RESTResponse.fromThrowable(e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - @NonNull - private static String makeQuery(@NonNull String[] data) throws UnsupportedEncodingException { - StringBuilder query = new StringBuilder(); - for (int i = 0; i < data.length; i = i + 2) { - query.append(URLEncoder.encode(data[i], "UTF-8")).append("=").append(URLEncoder.encode(data[i + 1], "UTF-8")).append("&"); - } - if (query.length() > 1) { - query.deleteCharAt(query.length()-1); - } - return query.toString(); - } - - public static boolean checkConnection(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo ni = cm.getActiveNetworkInfo(); - return ni != null && ni.isAvailable() && ni.isConnected(); - } - - private static boolean isOk(int responseCode) { - return responseCode >= 200 && responseCode < 300; - } - - public static boolean checkConnection(Context context, boolean onlyWiFi) { - ConnectivityManager cm = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); - if (cm == null) return false; - NetworkInfo ni = cm.getActiveNetworkInfo(); - return ni != null && ni.isConnected() && (!onlyWiFi || ni.getType() == ConnectivityManager.TYPE_WIFI); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/NoSSLv3SocketFactory.java b/app/src/main/java/org/nv95/openmanga/utils/NoSSLv3SocketFactory.java deleted file mode 100644 index 2c4b3ba2..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/NoSSLv3SocketFactory.java +++ /dev/null @@ -1,413 +0,0 @@ -package org.nv95.openmanga.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.channels.SocketChannel; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.net.ssl.HandshakeCompletedListener; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - -/** - * Created by admin on 10.07.17. - */ - -public class NoSSLv3SocketFactory extends SSLSocketFactory { - private static final NoSSLv3SocketFactory ourInstance = new NoSSLv3SocketFactory(); - - public static NoSSLv3SocketFactory getInstance() { - return ourInstance; - } - - private NoSSLv3SocketFactory() { - this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory(); - } - - private final SSLSocketFactory delegate; - - @Override - public String[] getDefaultCipherSuites() { - return delegate.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - private Socket makeSocketSafe(Socket socket) { - if (socket instanceof SSLSocket) { - socket = new NoSSLv3SSLSocket((SSLSocket) socket); - } - return socket; - } - - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return makeSocketSafe(delegate.createSocket(s, host, port, autoClose)); - } - - @Override - public Socket createSocket(String host, int port) throws IOException { - return makeSocketSafe(delegate.createSocket(host, port)); - } - - @Override - public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { - return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort)); - } - - @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - return makeSocketSafe(delegate.createSocket(host, port)); - } - - @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort)); - } - - private class NoSSLv3SSLSocket extends DelegateSSLSocket { - - private NoSSLv3SSLSocket(SSLSocket delegate) { - super(delegate); - - } - - @Override - public void setEnabledProtocols(String[] protocols) { - if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) { - - List enabledProtocols = new ArrayList<>(Arrays.asList(delegate.getEnabledProtocols())); - if (enabledProtocols.size() > 1) { - enabledProtocols.remove("SSLv3"); - System.out.println("Removed SSLv3 from enabled protocols"); - } else { - System.out.println("SSL stuck with protocol available for " + String.valueOf(enabledProtocols)); - } - protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]); - } - - super.setEnabledProtocols(protocols); - } - } - - public class DelegateSSLSocket extends SSLSocket { - - final SSLSocket delegate; - - DelegateSSLSocket(SSLSocket delegate) { - this.delegate = delegate; - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - @Override - public String[] getEnabledCipherSuites() { - return delegate.getEnabledCipherSuites(); - } - - @Override - public void setEnabledCipherSuites(String[] suites) { - delegate.setEnabledCipherSuites(suites); - } - - @Override - public String[] getSupportedProtocols() { - return delegate.getSupportedProtocols(); - } - - @Override - public String[] getEnabledProtocols() { - return delegate.getEnabledProtocols(); - } - - @Override - public void setEnabledProtocols(String[] protocols) { - delegate.setEnabledProtocols(protocols); - } - - @Override - public SSLSession getSession() { - return delegate.getSession(); - } - - @Override - public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { - delegate.addHandshakeCompletedListener(listener); - } - - @Override - public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { - delegate.removeHandshakeCompletedListener(listener); - } - - @Override - public void startHandshake() throws IOException { - delegate.startHandshake(); - } - - @Override - public void setUseClientMode(boolean mode) { - delegate.setUseClientMode(mode); - } - - @Override - public boolean getUseClientMode() { - return delegate.getUseClientMode(); - } - - @Override - public void setNeedClientAuth(boolean need) { - delegate.setNeedClientAuth(need); - } - - @Override - public void setWantClientAuth(boolean want) { - delegate.setWantClientAuth(want); - } - - @Override - public boolean getNeedClientAuth() { - return delegate.getNeedClientAuth(); - } - - @Override - public boolean getWantClientAuth() { - return delegate.getWantClientAuth(); - } - - @Override - public void setEnableSessionCreation(boolean flag) { - delegate.setEnableSessionCreation(flag); - } - - @Override - public boolean getEnableSessionCreation() { - return delegate.getEnableSessionCreation(); - } - - @Override - public void bind(SocketAddress localAddr) throws IOException { - delegate.bind(localAddr); - } - - @Override - public synchronized void close() throws IOException { - delegate.close(); - } - - @Override - public void connect(SocketAddress remoteAddr) throws IOException { - delegate.connect(remoteAddr); - } - - @Override - public void connect(SocketAddress remoteAddr, int timeout) throws IOException { - delegate.connect(remoteAddr, timeout); - } - - @Override - public SocketChannel getChannel() { - return delegate.getChannel(); - } - - @Override - public InetAddress getInetAddress() { - return delegate.getInetAddress(); - } - - @Override - public InputStream getInputStream() throws IOException { - return delegate.getInputStream(); - } - - @Override - public boolean getKeepAlive() throws SocketException { - return delegate.getKeepAlive(); - } - - @Override - public InetAddress getLocalAddress() { - return delegate.getLocalAddress(); - } - - @Override - public int getLocalPort() { - return delegate.getLocalPort(); - } - - @Override - public SocketAddress getLocalSocketAddress() { - return delegate.getLocalSocketAddress(); - } - - @Override - public boolean getOOBInline() throws SocketException { - return delegate.getOOBInline(); - } - - @Override - public OutputStream getOutputStream() throws IOException { - return delegate.getOutputStream(); - } - - @Override - public int getPort() { - return delegate.getPort(); - } - - @Override - public synchronized int getReceiveBufferSize() throws SocketException { - return delegate.getReceiveBufferSize(); - } - - @Override - public SocketAddress getRemoteSocketAddress() { - return delegate.getRemoteSocketAddress(); - } - - @Override - public boolean getReuseAddress() throws SocketException { - return delegate.getReuseAddress(); - } - - @Override - public synchronized int getSendBufferSize() throws SocketException { - return delegate.getSendBufferSize(); - } - - @Override - public int getSoLinger() throws SocketException { - return delegate.getSoLinger(); - } - - @Override - public synchronized int getSoTimeout() throws SocketException { - return delegate.getSoTimeout(); - } - - @Override - public boolean getTcpNoDelay() throws SocketException { - return delegate.getTcpNoDelay(); - } - - @Override - public int getTrafficClass() throws SocketException { - return delegate.getTrafficClass(); - } - - @Override - public boolean isBound() { - return delegate.isBound(); - } - - @Override - public boolean isClosed() { - return delegate.isClosed(); - } - - @Override - public boolean isConnected() { - return delegate.isConnected(); - } - - @Override - public boolean isInputShutdown() { - return delegate.isInputShutdown(); - } - - @Override - public boolean isOutputShutdown() { - return delegate.isOutputShutdown(); - } - - @Override - public void sendUrgentData(int value) throws IOException { - delegate.sendUrgentData(value); - } - - @Override - public void setKeepAlive(boolean keepAlive) throws SocketException { - delegate.setKeepAlive(keepAlive); - } - - @Override - public void setOOBInline(boolean oobinline) throws SocketException { - delegate.setOOBInline(oobinline); - } - - @Override - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { - delegate.setPerformancePreferences(connectionTime, latency, bandwidth); - } - - @Override - public synchronized void setReceiveBufferSize(int size) throws SocketException { - delegate.setReceiveBufferSize(size); - } - - @Override - public void setReuseAddress(boolean reuse) throws SocketException { - delegate.setReuseAddress(reuse); - } - - @Override - public synchronized void setSendBufferSize(int size) throws SocketException { - delegate.setSendBufferSize(size); - } - - @Override - public void setSoLinger(boolean on, int timeout) throws SocketException { - delegate.setSoLinger(on, timeout); - } - - @Override - public synchronized void setSoTimeout(int timeout) throws SocketException { - delegate.setSoTimeout(timeout); - } - - @Override - public void setTcpNoDelay(boolean on) throws SocketException { - delegate.setTcpNoDelay(on); - } - - @Override - public void setTrafficClass(int value) throws SocketException { - delegate.setTrafficClass(value); - } - - @Override - public void shutdownInput() throws IOException { - delegate.shutdownInput(); - } - - @Override - public void shutdownOutput() throws IOException { - delegate.shutdownOutput(); - } - - @Override - public String toString() { - return delegate.toString(); - } - - @Override - public boolean equals(Object o) { - return delegate.equals(o); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/OneShotNotifier.java b/app/src/main/java/org/nv95/openmanga/utils/OneShotNotifier.java deleted file mode 100644 index 499a4a2c..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/OneShotNotifier.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.content.SharedPreferences; - -/** - * Created by nv95 on 19.06.16. - */ - -public class OneShotNotifier { - - private final NotificationManager mNotificationManager; - private final SharedPreferences mPreferences; - - public OneShotNotifier(Context context) { - mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - mPreferences = context.getSharedPreferences("notif", Context.MODE_PRIVATE); - } - - public void notify(int id, Notification notification) { - mNotificationManager.notify(id, notification); - } - - public boolean isAlreadyShown(String key, int version) { - return mPreferences.getInt(key, -1) >= version; - } - - public boolean notifyOnce(int id, Notification notification, int version) { - String key = String.valueOf(id); - if (isAlreadyShown(key, version)) { - return false; - } - mNotificationManager.notify(id, notification); - mPreferences.edit() - .putInt(key, version) - .apply(); - return true; - } - - public void cancel(int id) { - mNotificationManager.cancel(id); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/PausableAsyncTask.java b/app/src/main/java/org/nv95/openmanga/utils/PausableAsyncTask.java deleted file mode 100644 index cb79e32f..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/PausableAsyncTask.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Message; -import android.support.annotation.UiThread; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Created by admin on 21.07.17. - */ - -public abstract class PausableAsyncTask extends AsyncTask { - - private final AtomicBoolean mPaused = new AtomicBoolean(false); - private final Handler mStateHandler = new Handler(new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case 1: - onResumed(); - break; - case -1: - onPaused(); - break; - } - return false; - } - }); - - public void setPaused(boolean value) { - if (getStatus() == Status.FINISHED) return; - boolean oldValue = mPaused.getAndSet(value); - if (value != oldValue) { - if (value) { - mStateHandler.sendEmptyMessage(-1); - } else { - mStateHandler.sendEmptyMessage(1); - } - } - } - - public void pause() { - setPaused(true); - } - - public void resume() { - setPaused(false); - } - - public boolean isPaused() { - return mPaused.get(); - } - - @UiThread - public void onPaused() {} - - @UiThread - public void onResumed() {} - - protected boolean waitForResume() { - while(isPaused()) { - try { - if (isCancelled()) { - return false; - } - Thread.sleep(100); - } catch (InterruptedException e) { - return false; - } - } - return !isCancelled(); - } - - public ExStatus getExStatus() { - Status status = getStatus(); - switch (status) { - case FINISHED: - return ExStatus.FINISHED; - case PENDING: - return ExStatus.PENDING; - default: - return isPaused() ? ExStatus.PAUSED : ExStatus.RUNNING; - } - } - - public boolean canCancel() { - return getStatus() != Status.FINISHED; - } - - public boolean isActive() { - return getStatus() == Status.RUNNING && !isPaused(); - } - - public enum ExStatus { - FINISHED, - PENDING, - RUNNING, - PAUSED - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/PreferencesUtils.java b/app/src/main/java/org/nv95/openmanga/utils/PreferencesUtils.java deleted file mode 100644 index 6480c9d1..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/PreferencesUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.components.IntegerPreference; - -/** - * Created by admin on 08.09.16. - */ - -public class PreferencesUtils { - - public static void bindPreferenceSummary(Preference preference) { - bindPreferenceSummary(preference, null, null); - } - - public static void bindPreferenceSummary(Preference preference, - @Nullable Preference.OnPreferenceChangeListener changeListener) { - bindPreferenceSummary(preference, changeListener, null); - } - - public static void bindPreferenceSummary(Preference preference, - @Nullable Preference.OnPreferenceChangeListener changeListener, - @Nullable String pattern) { - if (preference == null) { - return; - } - new SummaryHelper(changeListener, pattern).bind(preference); - } - - private static class SummaryHelper implements Preference.OnPreferenceChangeListener { - - @Nullable - private final Preference.OnPreferenceChangeListener mChangeListener; - @Nullable - private final String mPattern; - - private SummaryHelper(@Nullable Preference.OnPreferenceChangeListener changeListener, @Nullable String pattern) { - mChangeListener = changeListener; - mPattern = pattern; - } - - void bind(@NonNull Preference preference) { - if (preference instanceof EditTextPreference) { - preference.setSummary(formatSummary(((EditTextPreference) preference).getText())); - } else if (preference instanceof ListPreference) { - preference.setSummary(formatSummary( - ((ListPreference) preference).getEntries()[ - ((ListPreference) preference).findIndexOfValue(((ListPreference) preference).getValue()) - ].toString() - )); - } else if (preference instanceof IntegerPreference) { - preference.setSummary(formatSummary( - String.valueOf(((IntegerPreference)preference).getValue()) - )); - } else { - preference.setSummary(formatSummary(preference.getSharedPreferences() - .getString(preference.getKey(), null))); - } - preference.setOnPreferenceChangeListener(this); - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (mChangeListener != null && !mChangeListener.onPreferenceChange(preference, newValue)) { - return false; - } - if (preference instanceof ListPreference) { - int index = ((ListPreference) preference).findIndexOfValue((String) newValue); - String summ = ((ListPreference) preference).getEntries()[index].toString(); - preference.setSummary(formatSummary(summ)); - } else if (preference instanceof IntegerPreference) { - preference.setSummary(formatSummary( - String.valueOf(newValue) - )); - } else { - preference.setSummary(formatSummary((String) newValue)); - } - return true; - } - - private String formatSummary(String value) { - if (mPattern == null) { - return value; - } else { - return String.format(mPattern, value); - } - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/ProgressAsyncTask.java b/app/src/main/java/org/nv95/openmanga/utils/ProgressAsyncTask.java deleted file mode 100644 index 27ecf6a3..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/ProgressAsyncTask.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.BaseAppActivity; - -import java.lang.ref.WeakReference; - -/** - * Created by admin on 24.07.17. - */ - -public abstract class ProgressAsyncTask extends AsyncTask implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener { - - @Nullable - private ProgressDialog mDialog; - private final WeakReference mActivityRef; - - public ProgressAsyncTask(BaseAppActivity activity) { - mActivityRef = new WeakReference<>(activity); - mDialog = new ProgressDialog(activity); - mDialog.setMessage(activity.getString(R.string.loading)); - mDialog.setOnDismissListener(this); - mDialog.setOwnerActivity(activity); - mDialog.setOnCancelListener(this); - mDialog.setIndeterminate(true); - mDialog.setCancelable(true); - activity.registerLoaderTask(this); - } - - public void addCancelButton() { - if (mDialog != null) { - mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, - mDialog.getContext().getText(android.R.string.cancel), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.cancel(); - } - }); - } - } - - @Nullable - protected BaseAppActivity getActivity() { - return mActivityRef.get(); - } - - @Nullable - public ProgressDialog getDialog() { - return mDialog; - } - - public void setCancelable(boolean cancelable) { - if (mDialog != null) { - mDialog.setCancelable(cancelable); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - BaseAppActivity activity = getActivity(); - if (activity != null) { - if (mDialog != null) { - mDialog.show(); - } - onPreExecute(activity); - } - } - - @Override - protected void onPostExecute(Result result) { - super.onPostExecute(result); - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - BaseAppActivity activity = getActivity(); - if (activity != null) { - onPostExecute(activity, result); - } - } - - @Override - protected void onProgressUpdate(Progress[] values) { - super.onProgressUpdate(values); - BaseAppActivity activity = getActivity(); - if (activity != null) { - onProgressUpdate(activity, values); - } - } - - @SafeVarargs - public final void start(Param... params) { - this.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); - } - - private boolean canCancel() { - return getStatus() != Status.FINISHED; - } - - protected void onProgressUpdate(@NonNull BaseAppActivity activity, Progress[] values) { - } - - protected void onPreExecute(@NonNull BaseAppActivity activity) { - } - - protected void onPostExecute(@NonNull BaseAppActivity activity, Result result) { - } - - @Override - public void onDismiss(DialogInterface dialogInterface) { - mDialog = null; - } - - @Override - public void onCancel(DialogInterface dialogInterface) { - if (this.canCancel()) { - this.cancel(true); - } - mDialog = null; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/QuickReadTask.java b/app/src/main/java/org/nv95/openmanga/utils/QuickReadTask.java deleted file mode 100644 index 330efb0b..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/QuickReadTask.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.activities.ReadActivity2; -import org.nv95.openmanga.items.MangaInfo; -import org.nv95.openmanga.items.MangaSummary; -import org.nv95.openmanga.providers.HistoryProvider; -import org.nv95.openmanga.providers.LocalMangaProvider; -import org.nv95.openmanga.providers.MangaProvider; -import org.nv95.openmanga.providers.staff.MangaProviderManager; - -/** - * Created by admin on 18.07.17. - */ - -public class QuickReadTask extends AsyncTask implements DialogInterface.OnCancelListener { - - private final ProgressDialog mProgressDialog; - - public QuickReadTask(Context context) { - mProgressDialog = new ProgressDialog(context); - mProgressDialog.setIndeterminate(true); - mProgressDialog.setMessage(context.getString(R.string.loading)); - mProgressDialog.setOnCancelListener(this); - mProgressDialog.setCancelable(true); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mProgressDialog.show(); - } - - @Override - protected Bundle doInBackground(MangaInfo... mangaInfos) { - try { - MangaInfo manga = mangaInfos[0]; - MangaProvider provider; - if (manga.provider.equals(LocalMangaProvider.class)) { - provider = LocalMangaProvider.getInstance(mProgressDialog.getContext()); - } else { - if (!NetworkUtils.checkConnection(mProgressDialog.getContext())) { - provider = LocalMangaProvider.getInstance(mProgressDialog.getContext()); - manga = ((LocalMangaProvider)provider).getLocalManga(manga); - if (manga.provider != LocalMangaProvider.class) { - return null; - } - } else { - provider = MangaProviderManager.instanceProvider(mProgressDialog.getContext(), manga.provider); - } - } - MangaSummary summary = provider.getDetailedInfo(manga); - if (summary.chapters.isEmpty()) { - return null; - } - if (isCancelled()) { - return null; - } - Bundle bundle = new Bundle(); - bundle.putAll(summary.toBundle()); - HistoryProvider.HistorySummary hs = HistoryProvider.getInstance(mProgressDialog.getContext()).get(manga); - if (hs != null) { - int index = summary.chapters.indexByNumber(hs.getChapter()); - if (index != -1) { - bundle.putInt("chapter", index); - bundle.putInt("page", hs.getPage()); - } - } - return bundle; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(Bundle bundle) { - super.onPostExecute(bundle); - if (bundle != null) { - Context c = mProgressDialog.getContext(); - c.startActivity(new Intent(c, ReadActivity2.class).putExtras(bundle)); - } - mProgressDialog.dismiss(); - } - - @Override - public void onCancel(DialogInterface dialogInterface) { - this.cancel(false); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/SsivUtils.java b/app/src/main/java/org/nv95/openmanga/utils/SsivUtils.java deleted file mode 100644 index 4b8c20ca..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/SsivUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.graphics.PointF; - -import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; - -/** - * Created by nv95 on 18.11.16. - */ - -public class SsivUtils { - - public static void setScaleWidthTop(SubsamplingScaleImageView ssiv) { - ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); - ssiv.setMinScale(ssiv.getWidth() / (float)ssiv.getSWidth()); - ssiv.setScaleAndCenter( - ssiv.getMinScale(), - new PointF(ssiv.getSWidth() / 2f, 0) - ); - } - - public static void setScaleHeightLeft(SubsamplingScaleImageView ssiv) { - ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); - ssiv.setMinScale(ssiv.getHeight() / (float)ssiv.getSHeight()); - ssiv.setScaleAndCenter( - ssiv.getMinScale(), - new PointF(0, ssiv.getSHeight() / 2f) - ); - } - - public static void setScaleHeightRight(SubsamplingScaleImageView ssiv) { - ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); - ssiv.setMinScale(ssiv.getHeight() / (float)ssiv.getSHeight()); - ssiv.setScaleAndCenter( - ssiv.getMinScale(), - new PointF(ssiv.getSWidth(), ssiv.getSHeight() / 2f) - ); - } - - public static void setScaleFit(SubsamplingScaleImageView ssiv) { - ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE); - ssiv.resetScaleAndCenter(); - } - - public static void setScaleZoomSrc(SubsamplingScaleImageView ssiv) { - ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE); - ssiv.setScaleAndCenter( - ssiv.getMaxScale(), - new PointF(ssiv.getSWidth(), 0) - ); - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/StorageUpgradeTask.java b/app/src/main/java/org/nv95/openmanga/utils/StorageUpgradeTask.java deleted file mode 100644 index 1f4045c6..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/StorageUpgradeTask.java +++ /dev/null @@ -1,166 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.app.ProgressDialog; -import android.content.ContentValues; -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.AsyncTask; -import android.preference.PreferenceManager; - -import org.nv95.openmanga.R; -import org.nv95.openmanga.helpers.StorageHelper; - -import java.io.File; - -/** - * Created by nv95 on 04.06.16. - */ - -public class StorageUpgradeTask extends AsyncTask { - - private static final int STORAGE_VERSION = 1; - - private final ProgressDialog mProgressDialog; - private final Context mContext; - - public StorageUpgradeTask(Context context) { - mContext = context; - mProgressDialog = new ProgressDialog(mContext); - mProgressDialog.setCancelable(false); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - mProgressDialog.setMessage(mContext.getString(R.string.storage_upgrading)); - mProgressDialog.setIndeterminate(true); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mProgressDialog.show(); - } - - @Override - protected Boolean doInBackground(Void... params) { - boolean errors = false; - final StorageHelper storageHelper = new StorageHelper(mContext); - final MangaStore newStore = new MangaStore(mContext); - final SQLiteDatabase oldDatabase = storageHelper.getReadableDatabase(); - SQLiteDatabase newDatabase = null; - Cursor cursor = null; - Cursor cursor1 = null; - Cursor cursor2 = null; - ContentValues cv; - ContentValues cv1; - ContentValues cv2; - int mangaId; - int chapterId; - File cover; - //enum old saved manga - try { - if (!StorageHelper.isTableExists(oldDatabase, "local_storage")) { - oldDatabase.close(); - storageHelper.close(); - return true; - } - cursor = oldDatabase.query("local_storage", null, null, null, null, null, null); - if (cursor.moveToFirst()) { - boolean hasTimestamp = cursor.getColumnIndex("timestamp") != -1; - do { - //copy manga to new database - cv = new ContentValues(); - cv.put("id", mangaId = cursor.getInt(0)); - cv.put("name", cursor.getString(1)); - cv.put("subtitle", cursor.getString(2)); - cv.put("summary", cursor.getString(3)); - cv.put("description", cursor.getString(7)); - //move cover - cover = new File(cursor.getString(4)); - //noinspection ResultOfMethodCallIgnored - cover.renameTo(new File(cover.getParentFile(), "cover")); - cv.put("dir", cover.getParent()); - cv.put("timestamp", hasTimestamp ? cursor.getInt(8) : 0); - newDatabase = newStore.getDatabase(true); - newDatabase.insert(MangaStore.TABLE_MANGAS, null, cv); - //copy all chapters - cursor1 = oldDatabase.query("local_chapters", null, "mangaId=" + cursor.getString(6), null, null, null, null); - if (cursor1.moveToFirst()) { - do { - //copy chapter to new database - cv1 = new ContentValues(); - cv1.put("id", chapterId = cursor1.getInt(1)); - cv1.put("mangaid", mangaId); - cv1.put("name", cursor1.getString(3)); - cv1.put("number", cursor1.getInt(0)); - if (!newDatabase.isOpen()) { - newDatabase = newStore.getDatabase(true); - } - newDatabase.insert(MangaStore.TABLE_CHAPTERS, null, cv1); - //enum all pages - cursor2 = oldDatabase.query("local_pages", null, "chapterId=" + chapterId, null, null, null, null); - if (cursor2.moveToFirst()) { - do { - //copy page to new database - cv2 = new ContentValues(); - cv2.put("id", cursor2.getInt(1)); - cv2.put("chapterid", chapterId); - cv2.put("mangaid", mangaId); - cv2.put("file", chapterId + "/" + new File(cursor2.getString(3)).getName()); - cv2.put("number", cursor2.getInt(0)); - if (!newDatabase.isOpen()) { - newDatabase = newStore.getDatabase(true); - } - newDatabase.insert(MangaStore.TABLE_PAGES, null, cv2); - } while (cursor2.moveToNext()); - } - cursor2.close(); - cursor2 = null; - } while (cursor1.moveToNext()); - } - cursor1.close(); - cursor1 = null; - - } while (cursor.moveToNext()); - } - cursor.close(); - cursor = null; - } catch (Exception e) { - errors = true; - FileLogger.getInstance().report(e); - } finally { - if (cursor != null) { - cursor.close(); - } - if (cursor1 != null) { - cursor1.close(); - } - if (cursor2 != null) { - cursor2.close(); - } - } - if (newDatabase != null && newDatabase.isOpen()) { - newDatabase.close(); - } - oldDatabase.close(); - storageHelper.close(); - return !errors; - } - - @Override - protected void onPostExecute(Boolean aBoolean) { - super.onPostExecute(aBoolean); - mProgressDialog.dismiss(); - ChangesObserver.getInstance().emitOnLocalChanged(-1, null); - } - - public static void doUpgrade(Context context) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - if (prefs.getInt("storage_version", -1) < STORAGE_VERSION) { - new StorageUpgradeTask(context) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - prefs.edit() - .putInt("storage_version", STORAGE_VERSION) - .apply(); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/StorageUtils.java b/app/src/main/java/org/nv95/openmanga/utils/StorageUtils.java deleted file mode 100644 index 7b423ad8..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/StorageUtils.java +++ /dev/null @@ -1,273 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.MediaScannerConnection; -import android.media.ThumbnailUtils; -import android.net.Uri; -import android.os.Build; -import android.os.StatFs; -import android.support.annotation.NonNull; -import android.text.TextUtils; - -import org.nv95.openmanga.items.ThumbSize; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; - -/** - * Created by admin on 02.09.16. - */ - -public class StorageUtils { - - private static final int SIZE_MB = 1024 * 1024; - - @SuppressLint("DefaultLocale") - public static String formatSizeMb(int sizeMb) { - if (sizeMb < 1024) { - return sizeMb + " MB"; - } else { - return String.format("%.1f GB", sizeMb / 1024.f); - } - } - - public static int getFreeSpaceMb(String path) { - StatFs stat = new StatFs(path); - long aval = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - aval = stat.getAvailableBytes(); - } else { - aval = stat.getAvailableBlocks() * stat.getBlockSize(); - } - aval /= SIZE_MB; - return (int) aval; - } - - public static boolean moveDir(File source, String destination) { - if (source.isDirectory()) { - boolean res = true; - File[] list = source.listFiles(); - if (list != null) { - String dirDest = destination + File.separatorChar + source.getName(); - for (File o : list) { - res = (o.renameTo(new File(dirDest, o.getName())) || moveDir(o, dirDest)) && res; - } - } - source.delete(); - return res; - } else { - InputStream in = null; - OutputStream out = null; - try { - //create output directory if it doesn't exist - File dir = new File(destination); - if (!dir.exists()) { - dir.mkdirs(); - } - in = new FileInputStream(source); - out = new FileOutputStream(destination + File.separatorChar + source.getName()); - byte[] buffer = new byte[1024]; - int read; - while ((read = in.read(buffer)) != -1) { - out.write(buffer, 0, read); - } - // write the output file - out.flush(); - // delete the original file - source.delete(); - return true; - } catch (Exception e) { - return false; - } finally { - try { - if (in != null) { - in.close(); - } - if (out != null) { - out.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - public static void copyFile(File src, File dst) throws IOException { - InputStream in = null; - OutputStream out = null; - try { - in = new FileInputStream(src); - out = new FileOutputStream(dst); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - } finally { - if (in != null) { - in.close(); - } - if (out != null) { - out.close(); - } - } - } - - public static long dirSize(File dir) { - if (!dir.exists()) { - return 0; - } - long size = 0; - for (File file : dir.listFiles()) { - if (file.isFile()) { - size += file.length(); - } else - size += dirSize(file); - } - return size; - } - - public static boolean isImageFile(String name) { - int p = name.lastIndexOf("."); - if (p <= 0) { - return false; - } - String ext = name.substring(p + 1).toLowerCase(); - return "png".equals(ext) || "jpg".equals(ext) || "jpeg".equals(ext) || "webp".equals(ext); - } - - @NonNull - public static String tail(InputStream is, int maxLines) { - try { - BufferedReader r = new BufferedReader(new InputStreamReader(is)); - StringBuilder result = new StringBuilder(); - String line; - int i = maxLines; - while ((line = r.readLine()) != null && i > 0) { - result.append(line).append('\n'); - i--; - } - return result.toString(); - } catch (IOException e) { - return ""; - } - } - - public static void scanMediaFile(Context context, File file) { - if (!file.exists()) return; - MediaScannerConnection.scanFile(context, - new String[]{file.getPath()}, null, - new MediaScannerConnection.OnScanCompletedListener() { - @Override - public void onScanCompleted(String path, Uri uri) { - //.... - } - }); - } - - public static boolean saveBitmap(Bitmap bitmap, String filename) { - FileOutputStream out = null; - try { - out = new FileOutputStream(filename); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } finally { - try { - if (out != null) { - out.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static boolean copyThumbnail(String source, String dest, ThumbSize size) { - Bitmap full = BitmapFactory.decodeFile(source); - Bitmap thumb = ThumbnailUtils.extractThumbnail(full, size.getWidth(), size.getHeight()); - boolean res = saveBitmap(thumb, dest); - full.recycle(); - thumb.recycle(); - return res; - } - - public static List getAvailableStorages(Context context) { - final String appcat = TextUtils.join(File.separator, new String[]{"Android","data", context.getPackageName()}); - File storageRoot = new File("/storage"); - File[] storages = storageRoot.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - File appRoot = new File(file, appcat); - return appRoot.exists() && appRoot.canWrite(); - } - }); - return Arrays.asList(storages); - } - - public static File getFilesDir(Context context, File root, String type) { - final String appcat = TextUtils.join(File.separator, new String[]{"Android","data", context.getPackageName(), "files", type}); - File file = new File(root, appcat); - file.mkdirs(); - return file; - } - - @NonNull - public static String escapeFilename(String name) { - char escape = '_'; - int len = name.length(); - StringBuilder sb = new StringBuilder(len); - for (int i = 0; i < len; i++) { - char ch = name.charAt(i); - if ((ch >= '0' && ch <= '9') - || (ch >= 'A' && ch <= 'Z') - || (ch >= 'a' && ch <= 'z') - || (ch >= 'А' && ch <= 'ї') - || (ch == '_') - || (ch == '-') - || (ch == ' ') - ) { - sb.append(ch); - } else { - sb.append(escape); - if (ch < 0x10) { - sb.append('0'); - } - } - } - if (sb.length() == 0) { - return String.valueOf(name.hashCode()); - } - if (sb.length() > 40) { - sb.delete(40, sb.length() - 1); - } - return sb.toString(); - } - - public static File uniqueFile(File dir, String name) { - File file = new File(dir, name); - int i = 0; - while (file.exists()) { - int p = name.lastIndexOf("."); - String newName = name.substring(0, p) + " (" + i + ")." + name.substring(p + 1); - file = new File(dir, newName); - i++; - } - return file; - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/WeakAsyncTask.java b/app/src/main/java/org/nv95/openmanga/utils/WeakAsyncTask.java deleted file mode 100644 index b0130ca7..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/WeakAsyncTask.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.os.AsyncTask; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.nv95.openmanga.activities.BaseAppActivity; - -import java.lang.ref.WeakReference; - -/** - * Created by admin on 18.07.17. - */ - -public abstract class WeakAsyncTask extends AsyncTask { - - private final WeakReference mObjectRef; - - public WeakAsyncTask(Obj obj) { - mObjectRef = new WeakReference<>(obj); - } - - @Nullable - protected Obj getObject() { - return mObjectRef.get(); - } - - @Override - protected void onPostExecute(Result result) { - super.onPostExecute(result); - Obj obj = getObject(); - if (obj != null) { - onPostExecute(obj, result); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - Obj obj = getObject(); - if (obj != null) { - onPreExecute(obj); - } - } - - @Override - protected void onProgressUpdate(Progress[] values) { - super.onProgressUpdate(values); - Obj obj = getObject(); - if (obj != null) { - onProgressUpdate(obj, values); - } - } - - @SafeVarargs - public final void start(Param... params) { - this.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); - } - - public WeakAsyncTask attach(BaseAppActivity activity) { - activity.registerLoaderTask(this); - return this; - } - - public boolean canCancel() { - return getStatus() != Status.FINISHED; - } - - protected void onProgressUpdate(@NonNull Obj obj, Progress[] values) {} - - protected void onPreExecute(@NonNull Obj obj) {} - - protected void onPostExecute(@NonNull Obj obj, Result result) {} - - public static void cancel(@Nullable WeakReference weakReference, boolean mayInterruptIfRunning) { - if (weakReference == null) return; - AsyncTask task = weakReference.get(); - if (task != null && task.getStatus() != Status.FINISHED) { - task.cancel(mayInterruptIfRunning); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/ZipBuilder.java b/app/src/main/java/org/nv95/openmanga/utils/ZipBuilder.java deleted file mode 100644 index 6190eb1c..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/ZipBuilder.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.nv95.openmanga.utils; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -/** - * Created by nv95 on 14.01.16. - * Helps to pack files in zip archive easily - */ -public class ZipBuilder implements Closeable { - - private final ZipOutputStream mZipOutputStream; - private final File mOutputFile; - private final byte[] mBuffer = new byte[1024]; - - public ZipBuilder(File outputFile) throws IOException { - mZipOutputStream = new ZipOutputStream(new FileOutputStream(outputFile)); - mOutputFile = outputFile; - } - - @NonNull - public static ZipEntry[] enumerateEntries(String zipFile) throws Exception { - ZipFile file = null; - try { - ArrayList entryList = new ArrayList<>(); - file = new ZipFile(zipFile); - Enumeration entries = file.entries(); - while (entries.hasMoreElements()) { - ZipEntry o = entries.nextElement(); - entryList.add(o); - } - return entryList.toArray(new ZipEntry[entryList.size()]); - } finally { - if (file != null) { - file.close(); - } - } - } - - @Nullable - public static File[] unzipFiles(File file, File outputDir) { - final byte[] buffer = new byte[1024]; - if (!outputDir.exists() && !outputDir.mkdirs()) { - return null; - } - - ZipInputStream zipInputStream = null; - FileOutputStream outputStream = null; - try { - zipInputStream = new ZipInputStream(new FileInputStream(file)); - ArrayList files = new ArrayList<>(); - File outFile; - ZipEntry zipEntry; - while ((zipEntry = zipInputStream.getNextEntry()) != null) { - outFile = new File(outputDir, zipEntry.getName()); - if (outFile.exists() || outFile.createNewFile()) { - outputStream = new FileOutputStream(outFile); - int len; - while ((len = zipInputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, len); - } - outputStream.close(); - files.add(outFile); - } - } - return files.toArray(new File[files.size()]); - } catch (Exception e) { - FileLogger.getInstance().report("ZIP", e); - return null; - } finally { - try { - if (zipInputStream != null) { - zipInputStream.close(); - } - if (outputStream != null) { - outputStream.close(); - } - } catch (IOException e) { - FileLogger.getInstance().report("ZIP", e); - return null; - } - } - } - - public ZipBuilder addFile(File file) throws IOException { - return addFile(file, file.getName()); - } - - public ZipBuilder addFile(File file, String name) throws IOException { - FileInputStream in = null; - try { - ZipEntry zipEntry = new ZipEntry(name); - mZipOutputStream.putNextEntry(zipEntry); - in = new FileInputStream(file); - int len; - while ((len = in.read(mBuffer)) > 0) { - mZipOutputStream.write(mBuffer, 0, len); - } - mZipOutputStream.closeEntry(); - return this; - } finally { - if (in != null) { - in.close(); - } - } - } - - public ZipBuilder addFiles(File[] files) throws IOException { - for (File o : files) { - if (o.isFile()) { - addFile(o); - } - } - return this; - } - - public void build() throws IOException { - mZipOutputStream.finish(); - } - - public File getOutputFile() { - return mOutputFile; - } - - @Override - public void close() { - try { - mZipOutputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/choicecontrol/ModalChoiceCallback.java b/app/src/main/java/org/nv95/openmanga/utils/choicecontrol/ModalChoiceCallback.java deleted file mode 100644 index ac339c78..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/choicecontrol/ModalChoiceCallback.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.nv95.openmanga.utils.choicecontrol; - -import android.view.ActionMode; - -/** - * Created by nv95 on 30.06.16. - */ - -public interface ModalChoiceCallback extends ActionMode.Callback { - void onChoiceChanged(ActionMode actionMode, ModalChoiceController controller, int count); -} diff --git a/app/src/main/java/org/nv95/openmanga/utils/choicecontrol/ModalChoiceController.java b/app/src/main/java/org/nv95/openmanga/utils/choicecontrol/ModalChoiceController.java deleted file mode 100644 index 9304bc9a..00000000 --- a/app/src/main/java/org/nv95/openmanga/utils/choicecontrol/ModalChoiceController.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.nv95.openmanga.utils.choicecontrol; - -import android.annotation.SuppressLint; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.ActionMode; - -import org.nv95.openmanga.components.ExtraCheckable; - -import java.util.TreeSet; - -/** - * Created by nv95 on 30.06.16. - */ - -public class ModalChoiceController implements OnHolderClickListener { - - private boolean mEnabled; - private final TreeSet mSelected; - @Nullable - private ModalChoiceCallback mCallback; - @Nullable - private ActionMode mActionMode; - private final RecyclerView.Adapter mAdapter; - - @SuppressLint("UseSparseArrays") - public ModalChoiceController(RecyclerView.Adapter adapter) { - mSelected = new TreeSet<>(); - mActionMode = null; - mEnabled = false; - mAdapter = adapter; - } - - public int getSelectedItemsCount() { - return mSelected.size(); - } - - public void setCallback(ModalChoiceCallback callback) { - mCallback = callback; - } - - public void setEnabled(boolean enabled) { - mEnabled = enabled; - } - - public int[] getSelectedItemsPositions() { - Integer[] t = mSelected.toArray(new Integer[mSelected.size()]); - int[] res = new int[t.length]; - for (int i=0;i + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_cirno_1.png b/app/src/main/res/drawable-hdpi/ic_cirno_1.png new file mode 100644 index 00000000..75b2a89f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cirno_1.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_star.png b/app/src/main/res/drawable-hdpi/ic_stat_star.png index 4776c605..c0d15694 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_stat_star.png and b/app/src/main/res/drawable-hdpi/ic_stat_star.png differ diff --git a/app/src/main/res/drawable-hdpi/placeholder.png b/app/src/main/res/drawable-hdpi/placeholder.png new file mode 100644 index 00000000..c586fad9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/placeholder.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_cirno_1.png b/app/src/main/res/drawable-mdpi/ic_cirno_1.png new file mode 100644 index 00000000..74aff754 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cirno_1.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_star.png b/app/src/main/res/drawable-mdpi/ic_stat_star.png index 3e6b54d2..d4730690 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_stat_star.png and b/app/src/main/res/drawable-mdpi/ic_stat_star.png differ diff --git a/app/src/main/res/drawable-mdpi/placeholder.png b/app/src/main/res/drawable-mdpi/placeholder.png new file mode 100644 index 00000000..f45a6846 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/placeholder.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cirno_1.png b/app/src/main/res/drawable-xhdpi/ic_cirno_1.png new file mode 100644 index 00000000..6af07bf6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cirno_1.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_star.png b/app/src/main/res/drawable-xhdpi/ic_stat_star.png index 8a51fadd..88f08922 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_star.png and b/app/src/main/res/drawable-xhdpi/ic_stat_star.png differ diff --git a/app/src/main/res/drawable-xhdpi/placeholder.png b/app/src/main/res/drawable-xhdpi/placeholder.png new file mode 100644 index 00000000..e3639ab2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/placeholder.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cirno_1.png b/app/src/main/res/drawable-xxhdpi/ic_cirno_1.png new file mode 100644 index 00000000..99748e82 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cirno_1.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_star.png b/app/src/main/res/drawable-xxhdpi/ic_stat_star.png index 6a4a3bdd..1ddc220f 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_star.png and b/app/src/main/res/drawable-xxhdpi/ic_stat_star.png differ diff --git a/app/src/main/res/drawable-xxhdpi/placeholder.png b/app/src/main/res/drawable-xxhdpi/placeholder.png new file mode 100644 index 00000000..bc30be9a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/placeholder.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_cirno_1.png b/app/src/main/res/drawable-xxxhdpi/ic_cirno_1.png new file mode 100644 index 00000000..629c1359 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_cirno_1.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/placeholder.png b/app/src/main/res/drawable-xxxhdpi/placeholder.png old mode 100755 new mode 100644 index 7b9a58cc..e9b9b6d8 Binary files a/app/src/main/res/drawable-xxxhdpi/placeholder.png and b/app/src/main/res/drawable-xxxhdpi/placeholder.png differ diff --git a/app/src/main/res/drawable/circle_gray.xml b/app/src/main/res/drawable/circle_gray.xml new file mode 100644 index 00000000..76efb86a --- /dev/null +++ b/app/src/main/res/drawable/circle_gray.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_blue.xml b/app/src/main/res/drawable/gradient_blue.xml new file mode 100644 index 00000000..911fc93c --- /dev/null +++ b/app/src/main/res/drawable/gradient_blue.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_green.xml b/app/src/main/res/drawable/gradient_green.xml new file mode 100644 index 00000000..e7a17c6f --- /dev/null +++ b/app/src/main/res/drawable/gradient_green.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_red.xml b/app/src/main/res/drawable/gradient_red.xml new file mode 100644 index 00000000..fb05ef46 --- /dev/null +++ b/app/src/main/res/drawable/gradient_red.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_white.xml b/app/src/main/res/drawable/ic_add_white.xml new file mode 100644 index 00000000..b75bf6aa --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_appearance_white.xml b/app/src/main/res/drawable/ic_appearance_white.xml new file mode 100644 index 00000000..28da8342 --- /dev/null +++ b/app/src/main/res/drawable/ic_appearance_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 00000000..0783aa49 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pref_reader.xml b/app/src/main/res/drawable/ic_aspect_ratio_black.xml similarity index 55% rename from app/src/main/res/drawable/ic_pref_reader.xml rename to app/src/main/res/drawable/ic_aspect_ratio_black.xml index 3fd5a4f6..2f3bc916 100644 --- a/app/src/main/res/drawable/ic_pref_reader.xml +++ b/app/src/main/res/drawable/ic_aspect_ratio_black.xml @@ -5,5 +5,5 @@ android:viewportHeight="24.0"> + android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/> diff --git a/app/src/main/res/drawable/ic_battery_20_black.xml b/app/src/main/res/drawable/ic_battery_20_black.xml new file mode 100644 index 00000000..65ce6224 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_20_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_40_black.xml b/app/src/main/res/drawable/ic_battery_40_black.xml new file mode 100644 index 00000000..27ff6ee0 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_40_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_60_black.xml b/app/src/main/res/drawable/ic_battery_60_black.xml new file mode 100644 index 00000000..9eab8186 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_60_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_80_black.xml b/app/src/main/res/drawable/ic_battery_80_black.xml new file mode 100644 index 00000000..38371a08 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_80_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_alert_black.xml b/app/src/main/res/drawable/ic_battery_alert_black.xml new file mode 100644 index 00000000..57bcc1dd --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_alert_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_black.xml b/app/src/main/res/drawable/ic_battery_black.xml new file mode 100644 index 00000000..3d71433d --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_charging_20_black.xml b/app/src/main/res/drawable/ic_battery_charging_20_black.xml new file mode 100644 index 00000000..db368773 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_charging_20_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_charging_40_black.xml b/app/src/main/res/drawable/ic_battery_charging_40_black.xml new file mode 100644 index 00000000..51361ecb --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_charging_40_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_charging_60_black.xml b/app/src/main/res/drawable/ic_battery_charging_60_black.xml new file mode 100644 index 00000000..cbba0dc1 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_charging_60_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_charging_90_black.xml b/app/src/main/res/drawable/ic_battery_charging_90_black.xml new file mode 100644 index 00000000..bad49d76 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_charging_90_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_pref_more.xml b/app/src/main/res/drawable/ic_bookmark_add_black.xml similarity index 54% rename from app/src/main/res/drawable/ic_pref_more.xml rename to app/src/main/res/drawable/ic_bookmark_add_black.xml index da83afdb..d10772fd 100644 --- a/app/src/main/res/drawable/ic_pref_more.xml +++ b/app/src/main/res/drawable/ic_bookmark_add_black.xml @@ -5,5 +5,5 @@ android:viewportHeight="24.0"> + android:pathData="M17,18V5H7V18L12,15.82L17,18M17,3A2,2 0,0 1,19 5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M11,7H13V9H15V11H13V13H11V11H9V9H11V7Z"/> diff --git a/app/src/main/res/drawable/ic_bookmark_remove_black.xml b/app/src/main/res/drawable/ic_bookmark_remove_black.xml new file mode 100644 index 00000000..f40360d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_remove_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_bookmark_light.xml b/app/src/main/res/drawable/ic_bookmark_white.xml similarity index 100% rename from app/src/main/res/drawable/ic_bookmark_light.xml rename to app/src/main/res/drawable/ic_bookmark_white.xml diff --git a/app/src/main/res/drawable/ic_delete_light.xml b/app/src/main/res/drawable/ic_braces_white.xml similarity index 66% rename from app/src/main/res/drawable/ic_delete_light.xml rename to app/src/main/res/drawable/ic_braces_white.xml index 55c8fa4f..219989a2 100644 --- a/app/src/main/res/drawable/ic_delete_light.xml +++ b/app/src/main/res/drawable/ic_braces_white.xml @@ -5,5 +5,5 @@ android:viewportHeight="24.0"> + android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/> diff --git a/app/src/main/res/drawable/ic_bug_red.xml b/app/src/main/res/drawable/ic_bug_red.xml new file mode 100644 index 00000000..64848a04 --- /dev/null +++ b/app/src/main/res/drawable/ic_bug_red.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cancel_darker.xml b/app/src/main/res/drawable/ic_cancel_black.xml similarity index 90% rename from app/src/main/res/drawable/ic_cancel_darker.xml rename to app/src/main/res/drawable/ic_cancel_black.xml index 48aeafc0..02ecfc1d 100644 --- a/app/src/main/res/drawable/ic_cancel_darker.xml +++ b/app/src/main/res/drawable/ic_cancel_black.xml @@ -4,6 +4,6 @@ android:viewportHeight="24.0" android:viewportWidth="24.0"> diff --git a/app/src/main/res/drawable/ic_clear_all_white.xml b/app/src/main/res/drawable/ic_clear_all_white.xml new file mode 100644 index 00000000..e5708f58 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_all_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud_sync_white.xml b/app/src/main/res/drawable/ic_cloud_sync_white.xml new file mode 100644 index 00000000..2ccf1558 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_sync_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_collapse_black.xml b/app/src/main/res/drawable/ic_collapse_black.xml new file mode 100644 index 00000000..3afdf968 --- /dev/null +++ b/app/src/main/res/drawable/ic_collapse_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_compass_black.xml b/app/src/main/res/drawable/ic_compass_black.xml new file mode 100644 index 00000000..92a723c8 --- /dev/null +++ b/app/src/main/res/drawable/ic_compass_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_dice_white.xml b/app/src/main/res/drawable/ic_dice_white.xml new file mode 100644 index 00000000..3c862185 --- /dev/null +++ b/app/src/main/res/drawable/ic_dice_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_discover_green.xml b/app/src/main/res/drawable/ic_discover_green.xml new file mode 100644 index 00000000..9cd0eaa8 --- /dev/null +++ b/app/src/main/res/drawable/ic_discover_green.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_done_all_light.xml b/app/src/main/res/drawable/ic_done_all_white.xml similarity index 100% rename from app/src/main/res/drawable/ic_done_all_light.xml rename to app/src/main/res/drawable/ic_done_all_white.xml diff --git a/app/src/main/res/drawable/ic_done_white.xml b/app/src/main/res/drawable/ic_done_white.xml new file mode 100644 index 00000000..bd13393e --- /dev/null +++ b/app/src/main/res/drawable/ic_done_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download_white.xml b/app/src/main/res/drawable/ic_download_white.xml new file mode 100644 index 00000000..a9a8adeb --- /dev/null +++ b/app/src/main/res/drawable/ic_download_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pref_home.xml b/app/src/main/res/drawable/ic_expand_black.xml similarity index 77% rename from app/src/main/res/drawable/ic_pref_home.xml rename to app/src/main/res/drawable/ic_expand_black.xml index 70fb2910..8d57dbc1 100644 --- a/app/src/main/res/drawable/ic_pref_home.xml +++ b/app/src/main/res/drawable/ic_expand_black.xml @@ -5,5 +5,5 @@ android:viewportHeight="24.0"> + android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/> diff --git a/app/src/main/res/drawable/ic_file_image_white.xml b/app/src/main/res/drawable/ic_file_image_white.xml new file mode 100644 index 00000000..493a2f4f --- /dev/null +++ b/app/src/main/res/drawable/ic_file_image_white.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_white.xml b/app/src/main/res/drawable/ic_file_white.xml new file mode 100644 index 00000000..66a13b4f --- /dev/null +++ b/app/src/main/res/drawable/ic_file_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_filter_list_white.xml b/app/src/main/res/drawable/ic_filter_list_white.xml new file mode 100644 index 00000000..58d6fd3b --- /dev/null +++ b/app/src/main/res/drawable/ic_filter_list_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_folder_white.xml b/app/src/main/res/drawable/ic_folder_white.xml new file mode 100644 index 00000000..f27de003 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_list_checks_white.xml b/app/src/main/res/drawable/ic_format_list_checks_white.xml new file mode 100644 index 00000000..4a23fd9f --- /dev/null +++ b/app/src/main/res/drawable/ic_format_list_checks_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_help_white.xml b/app/src/main/res/drawable/ic_help_white.xml new file mode 100644 index 00000000..e37d019c --- /dev/null +++ b/app/src/main/res/drawable/ic_help_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_white.xml b/app/src/main/res/drawable/ic_home_white.xml new file mode 100644 index 00000000..0c8ca19d --- /dev/null +++ b/app/src/main/res/drawable/ic_home_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lightbulb_white.xml b/app/src/main/res/drawable/ic_lightbulb_white.xml new file mode 100644 index 00000000..d4d50f6d --- /dev/null +++ b/app/src/main/res/drawable/ic_lightbulb_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_list_numbered_white.xml b/app/src/main/res/drawable/ic_list_numbered_white.xml new file mode 100644 index 00000000..c9e6c11f --- /dev/null +++ b/app/src/main/res/drawable/ic_list_numbered_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_white.xml b/app/src/main/res/drawable/ic_menu_white.xml new file mode 100644 index 00000000..f21ae134 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_white.xml b/app/src/main/res/drawable/ic_more_white.xml new file mode 100644 index 00000000..98ad5560 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_network_white.xml b/app/src/main/res/drawable/ic_network_white.xml new file mode 100644 index 00000000..88fc43d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_network_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_notify_new_white.xml b/app/src/main/res/drawable/ic_notify_new_white.xml new file mode 100644 index 00000000..0c5adfe3 --- /dev/null +++ b/app/src/main/res/drawable/ic_notify_new_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_open_in_black.xml b/app/src/main/res/drawable/ic_open_in_black.xml new file mode 100644 index 00000000..60b75a54 --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 00000000..9cebf2a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_play_green.xml b/app/src/main/res/drawable/ic_play_green.xml new file mode 100644 index 00000000..4c0640c9 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_green.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pref_appearance.xml b/app/src/main/res/drawable/ic_pref_appearance.xml deleted file mode 100644 index f75e2fbe..00000000 --- a/app/src/main/res/drawable/ic_pref_appearance.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_pref_cheknew.xml b/app/src/main/res/drawable/ic_pref_cheknew.xml deleted file mode 100644 index 3e8e1c0e..00000000 --- a/app/src/main/res/drawable/ic_pref_cheknew.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_pref_sources.xml b/app/src/main/res/drawable/ic_pref_sources.xml deleted file mode 100644 index d976b424..00000000 --- a/app/src/main/res/drawable/ic_pref_sources.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_pref_sync.xml b/app/src/main/res/drawable/ic_pref_sync.xml deleted file mode 100644 index a782e8e1..00000000 --- a/app/src/main/res/drawable/ic_pref_sync.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_read_white.xml b/app/src/main/res/drawable/ic_read_white.xml new file mode 100644 index 00000000..fce4750d --- /dev/null +++ b/app/src/main/res/drawable/ic_read_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_refresh_white.xml b/app/src/main/res/drawable/ic_refresh_white.xml new file mode 100644 index 00000000..5cbdf6ec --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_reorder_black.xml b/app/src/main/res/drawable/ic_reorder_black.xml new file mode 100644 index 00000000..2b87cd84 --- /dev/null +++ b/app/src/main/res/drawable/ic_reorder_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_save_light.xml b/app/src/main/res/drawable/ic_save_white.xml similarity index 100% rename from app/src/main/res/drawable/ic_save_light.xml rename to app/src/main/res/drawable/ic_save_white.xml diff --git a/app/src/main/res/drawable/ic_sdcard_white.xml b/app/src/main/res/drawable/ic_sdcard_white.xml new file mode 100644 index 00000000..be5e92d8 --- /dev/null +++ b/app/src/main/res/drawable/ic_sdcard_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_dkgray.xml b/app/src/main/res/drawable/ic_search_dkgray.xml new file mode 100644 index 00000000..1181d957 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_dkgray.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_light.xml b/app/src/main/res/drawable/ic_search_white.xml similarity index 100% rename from app/src/main/res/drawable/ic_search_light.xml rename to app/src/main/res/drawable/ic_search_white.xml diff --git a/app/src/main/res/drawable/ic_settings_black.xml b/app/src/main/res/drawable/ic_settings_black.xml new file mode 100644 index 00000000..ace746c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share_light.xml b/app/src/main/res/drawable/ic_share_light.xml deleted file mode 100644 index e921ed77..00000000 --- a/app/src/main/res/drawable/ic_share_light.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_share_white.xml b/app/src/main/res/drawable/ic_share_white.xml new file mode 100644 index 00000000..8065ae95 --- /dev/null +++ b/app/src/main/res/drawable/ic_share_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sort_numeric_reverse_white.xml b/app/src/main/res/drawable/ic_sort_numeric_reverse_white.xml new file mode 100644 index 00000000..40342c2d --- /dev/null +++ b/app/src/main/res/drawable/ic_sort_numeric_reverse_white.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_sort_numeric_white.xml b/app/src/main/res/drawable/ic_sort_numeric_white.xml new file mode 100644 index 00000000..d30bfb43 --- /dev/null +++ b/app/src/main/res/drawable/ic_sort_numeric_white.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_star_white.xml b/app/src/main/res/drawable/ic_star_white.xml new file mode 100644 index 00000000..fd89898f --- /dev/null +++ b/app/src/main/res/drawable/ic_star_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_storage_black.xml b/app/src/main/res/drawable/ic_storage_black.xml new file mode 100644 index 00000000..53c595cd --- /dev/null +++ b/app/src/main/res/drawable/ic_storage_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sync_white.xml b/app/src/main/res/drawable/ic_sync_white.xml new file mode 100644 index 00000000..68519a2c --- /dev/null +++ b/app/src/main/res/drawable/ic_sync_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_tag_black.xml b/app/src/main/res/drawable/ic_tag_black.xml new file mode 100644 index 00000000..8b19fe42 --- /dev/null +++ b/app/src/main/res/drawable/ic_tag_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_tag_heart_black.xml b/app/src/main/res/drawable/ic_tag_heart_black.xml new file mode 100644 index 00000000..af3ab2d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_tag_heart_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_tools_black.xml b/app/src/main/res/drawable/ic_tools_black.xml new file mode 100644 index 00000000..18f56766 --- /dev/null +++ b/app/src/main/res/drawable/ic_tools_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_trash_white.xml b/app/src/main/res/drawable/ic_trash_white.xml new file mode 100644 index 00000000..f29c1c74 --- /dev/null +++ b/app/src/main/res/drawable/ic_trash_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_view_grid_white.xml b/app/src/main/res/drawable/ic_view_grid_white.xml index b36735c3..cb9f3d43 100644 --- a/app/src/main/res/drawable/ic_view_grid_white.xml +++ b/app/src/main/res/drawable/ic_view_grid_white.xml @@ -1,9 +1,9 @@ - + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + diff --git a/app/src/main/res/drawable/ic_view_list_white.xml b/app/src/main/res/drawable/ic_view_list_white.xml new file mode 100644 index 00000000..14a34c44 --- /dev/null +++ b/app/src/main/res/drawable/ic_view_list_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wizard_blue.xml b/app/src/main/res/drawable/ic_wizard_blue.xml new file mode 100644 index 00000000..cf72aabc --- /dev/null +++ b/app/src/main/res/drawable/ic_wizard_blue.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/line.xml b/app/src/main/res/drawable/line.xml new file mode 100644 index 00000000..813f25a6 --- /dev/null +++ b/app/src/main/res/drawable/line.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/line_drawable.xml b/app/src/main/res/drawable/line_drawable.xml new file mode 100644 index 00000000..7a2bbbb4 --- /dev/null +++ b/app/src/main/res/drawable/line_drawable.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/thumb.xml b/app/src/main/res/drawable/thumb.xml new file mode 100644 index 00000000..c4d8d7ae --- /dev/null +++ b/app/src/main/res/drawable/thumb.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/thumb_color.xml b/app/src/main/res/drawable/thumb_color.xml new file mode 100644 index 00000000..02e4d444 --- /dev/null +++ b/app/src/main/res/drawable/thumb_color.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/thumb_drawable.xml b/app/src/main/res/drawable/thumb_drawable.xml new file mode 100644 index 00000000..965d2a87 --- /dev/null +++ b/app/src/main/res/drawable/thumb_drawable.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-large-land/activity_preview2.xml b/app/src/main/res/layout-large-land/activity_preview2.xml index 888d49cc..a86b58e5 100644 --- a/app/src/main/res/layout-large-land/activity_preview2.xml +++ b/app/src/main/res/layout-large-land/activity_preview2.xml @@ -109,7 +109,7 @@ android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> - + android:text="@string/settings" /> diff --git a/app/src/main/res/layout-large-land/fragment_tools.xml b/app/src/main/res/layout-large-land/fragment_tools.xml new file mode 100644 index 00000000..d3959a49 --- /dev/null +++ b/app/src/main/res/layout-large-land/fragment_tools.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-large-land/page_manga_details.xml b/app/src/main/res/layout-large-land/page_manga_details.xml new file mode 100644 index 00000000..ff035f69 --- /dev/null +++ b/app/src/main/res/layout-large-land/page_manga_details.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + +