preSelectedAttributes = null;
if (intent != null && intent.getExtras() != null) {
- SearchActivityBundle.Parser parser = new SearchActivityBundle.Parser(intent.getExtras());
- Uri searchUri = parser.getUri();
+ SearchActivityBundle parser = new SearchActivityBundle(intent.getExtras());
+ Uri searchUri = Uri.parse(parser.getUri());
excludeClicked = parser.getExcludeMode();
if (searchUri != null)
- preSelectedAttributes = SearchActivityBundle.Parser.parseSearchUri(searchUri);
+ preSelectedAttributes = SearchActivityBundle.Companion.parseSearchUri(searchUri);
}
setContentView(R.layout.activity_search);
@@ -105,7 +105,7 @@ protected void onCreate(Bundle savedInstanceState) {
anyTypeButton.setEnabled(true);
tagTypeButton = findViewById(R.id.textCategoryTag);
- tagTypeButton.setOnClickListener(v -> onAttrButtonClick(excludeClicked,AttributeType.TAG));
+ tagTypeButton.setOnClickListener(v -> onAttrButtonClick(excludeClicked, AttributeType.TAG));
artistTypeButton = findViewById(R.id.textCategoryArtist);
@@ -168,8 +168,8 @@ private void onQueryUpdated(@NonNull final SparseIntArray attrCount) {
updateAttributeTypeButton(sourceTypeButton, attrCount, AttributeType.SOURCE);
}
- public void onExcludeClick(View view){
- excludeClicked = ((CheckBox) view).isChecked();
+ public void onExcludeClick(View view) {
+ excludeClicked = ((CheckBox) view).isChecked();
}
/**
@@ -246,10 +246,11 @@ private void onBooksCounted(int count) {
* Transmit the search query to the library screen and close the advanced search screen
*/
private void searchBooks() {
- Uri searchUri = SearchActivityBundle.Builder.buildSearchUri(viewModel.getSelectedAttributesData().getValue());
+ Uri searchUri = SearchActivityBundle.Companion.buildSearchUri(viewModel.getSelectedAttributesData().getValue());
Timber.d("URI :%s", searchUri);
- SearchActivityBundle.Builder builder = new SearchActivityBundle.Builder().setUri(searchUri);
+ SearchActivityBundle builder = new SearchActivityBundle();
+ builder.setUri(searchUri.toString());
builder.setExcludeMode(excludeClicked);
Intent returnIntent = new Intent();
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/BaseWebActivityBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/BaseWebActivityBundle.kt
index c804735a0e..cee5b723e5 100644
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/BaseWebActivityBundle.kt
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/BaseWebActivityBundle.kt
@@ -7,11 +7,7 @@ import me.devsaki.hentoid.util.string
* Helper class to transfer data from any Activity to {@link me.devsaki.hentoid.activities.PrefsActivity}
* through a Bundle
*/
-class BaseWebActivityBundle(private val bundle: Bundle) {
-
- constructor() : this(Bundle())
+class BaseWebActivityBundle(val bundle: Bundle = Bundle()) {
var url by bundle.string(default = "")
-
- fun toBundle() = bundle
}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ContentItemBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ContentItemBundle.java
deleted file mode 100644
index 3d060d5c01..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ContentItemBundle.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package me.devsaki.hentoid.activities.bundles;
-
-import android.os.Bundle;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-/**
- * Helper class to transfer payload data to {@link me.devsaki.hentoid.viewholders.ContentItem}
- * through a Bundle
- *
- * Use Builder class to set data; use Parser class to get data
- */
-public class ContentItemBundle {
- private static final String KEY_DELETE_PROCESSING = "is_being_deleted";
- private static final String KEY_FAV_STATE = "favourite";
- private static final String KEY_READS = "reads";
- private static final String KEY_READ_COUNT = "read_count";
- private static final String KEY_COVER_URI = "cover_uri";
- private static final String KEY_COMPL_STATE = "completed";
- private static final String KEY_TITLE = "title";
-
- private ContentItemBundle() {
- throw new UnsupportedOperationException();
- }
-
- public static final class Builder {
-
- private final Bundle bundle = new Bundle();
-
- public void setIsBeingDeleted(boolean isBeingDeleted) {
- bundle.putBoolean(KEY_DELETE_PROCESSING, isBeingDeleted);
- }
-
- public void setIsFavourite(boolean isFavourite) {
- bundle.putBoolean(KEY_FAV_STATE, isFavourite);
- }
-
- public void setIsCompleted(boolean isCompleted) {
- bundle.putBoolean(KEY_COMPL_STATE, isCompleted);
- }
-
- public void setReads(long reads) {
- bundle.putLong(KEY_READS, reads);
- }
-
- public void setReadPagesCount(long readPagesCount) {
- bundle.putLong(KEY_READ_COUNT, readPagesCount);
- }
-
- public void setCoverUri(String uri) {
- bundle.putString(KEY_COVER_URI, uri);
- }
-
- public void setTitle(String value) {
- bundle.putString(KEY_TITLE, value);
- }
-
- public boolean isEmpty() {
- return bundle.isEmpty();
- }
-
- public Bundle getBundle() {
- return bundle;
- }
-
-
- }
-
- public static final class Parser {
-
- private final Bundle bundle;
-
- public Parser(@Nonnull Bundle bundle) {
- this.bundle = bundle;
- }
-
- @Nullable
- public Boolean isBeingDeleted() {
- if (bundle.containsKey(KEY_DELETE_PROCESSING))
- return bundle.getBoolean(KEY_DELETE_PROCESSING);
- else return null;
- }
-
- @Nullable
- public Boolean isFavourite() {
- if (bundle.containsKey(KEY_FAV_STATE)) return bundle.getBoolean(KEY_FAV_STATE);
- else return null;
- }
-
- public Boolean isCompleted() {
- if (bundle.containsKey(KEY_COMPL_STATE)) return bundle.getBoolean(KEY_COMPL_STATE);
- else return null;
- }
-
- @Nullable
- public Long getReads() {
- if (bundle.containsKey(KEY_READS)) return bundle.getLong(KEY_READS);
- else return null;
- }
-
- @Nullable
- public Long getReadPagesCount() {
- if (bundle.containsKey(KEY_READ_COUNT)) return bundle.getLong(KEY_READ_COUNT);
- else return null;
- }
-
- @Nullable
- public String getCoverUri() {
- if (bundle.containsKey(KEY_COVER_URI)) return bundle.getString(KEY_COVER_URI);
- else return null;
- }
-
- @Nullable
- public String getTitle() {
- if (bundle.containsKey(KEY_TITLE)) return bundle.getString(KEY_TITLE);
- else return null;
- }
- }
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ContentItemBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ContentItemBundle.kt
new file mode 100644
index 0000000000..0786a8bb50
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ContentItemBundle.kt
@@ -0,0 +1,30 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.os.Bundle
+import me.devsaki.hentoid.util.boolean
+import me.devsaki.hentoid.util.int
+import me.devsaki.hentoid.util.long
+import me.devsaki.hentoid.util.string
+
+/**
+ * Helper class to transfer payload data to [me.devsaki.hentoid.viewholders.ContentItem]
+ * through a Bundle
+ */
+class ContentItemBundle(val bundle: Bundle = Bundle()) {
+
+ var isBeingDeleted by bundle.boolean()
+
+ var isFavourite by bundle.boolean()
+
+ var isCompleted by bundle.boolean()
+
+ var reads by bundle.long()
+
+ var readPagesCount by bundle.int()
+
+ var coverUri by bundle.string()
+
+ var title by bundle.string()
+
+ val isEmpty get() = bundle.isEmpty
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.java
deleted file mode 100644
index 3719e7383f..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package me.devsaki.hentoid.activities.bundles;
-
-import android.os.Bundle;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-/**
- * Helper class to transfer payload data to {@link me.devsaki.hentoid.viewholders.DuplicateItem}
- * through a Bundle
- *
- * Use Builder class to set data; use Parser class to get data
- */
-public class DuplicateItemBundle {
- private static final String KEY_KEEP = "keep";
- private static final String KEY_IS_BEING_DELETED = "isBeingDeleted";
-
- private DuplicateItemBundle() {
- throw new UnsupportedOperationException();
- }
-
- public static final class Builder {
-
- private final Bundle bundle = new Bundle();
-
- public void setKeep(Boolean value) {
- if (value != null)
- bundle.putBoolean(KEY_KEEP, value);
- }
-
- public void setIsBeingDeleted(Boolean value) {
- if (value != null)
- bundle.putBoolean(KEY_IS_BEING_DELETED, value);
- }
-
- public boolean isEmpty() {
- return bundle.isEmpty();
- }
-
- public Bundle getBundle() {
- return bundle;
- }
- }
-
- public static final class Parser {
-
- private final Bundle bundle;
-
- public Parser(@Nonnull Bundle bundle) {
- this.bundle = bundle;
- }
-
- @Nullable
- public Boolean getKeep() {
- if (bundle.containsKey(KEY_KEEP)) return bundle.getBoolean(KEY_KEEP);
- else return null;
- }
-
- @Nullable
- public Boolean isBeingDeleted() {
- if (bundle.containsKey(KEY_IS_BEING_DELETED))
- return bundle.getBoolean(KEY_IS_BEING_DELETED);
- else return null;
- }
- }
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.kt
new file mode 100644
index 0000000000..2c5b84f1f0
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/DuplicateItemBundle.kt
@@ -0,0 +1,17 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.os.Bundle
+import me.devsaki.hentoid.util.boolean
+
+/**
+ * Helper class to transfer payload data to [me.devsaki.hentoid.viewholders.DuplicateItem]
+ * through a Bundle
+ */
+class DuplicateItemBundle(val bundle: Bundle = Bundle()) {
+
+ var isKeep by bundle.boolean()
+
+ var isBeingDeleted by bundle.boolean()
+
+ val isEmpty get() = bundle.isEmpty
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/GroupItemBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/GroupItemBundle.java
deleted file mode 100644
index 20c0cba3ef..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/GroupItemBundle.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package me.devsaki.hentoid.activities.bundles;
-
-import android.os.Bundle;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-/**
- * Helper class to transfer payload data to {@link me.devsaki.hentoid.viewholders.GroupDisplayItem}
- * through a Bundle
- *
- * Use Builder class to set data; use Parser class to get data
- */
-public class GroupItemBundle {
- private static final String KEY_PICTURE = "picture";
- private static final String KEY_FAV = "favourite";
-
- private GroupItemBundle() {
- throw new UnsupportedOperationException();
- }
-
- public static final class Builder {
-
- private final Bundle bundle = new Bundle();
-
- public void setCoverUri(String uri) {
- bundle.putString(KEY_PICTURE, uri);
- }
-
- public void setFavourite(boolean isFavourite) {
- bundle.putBoolean(KEY_FAV, isFavourite);
- }
-
- public boolean isEmpty() {
- return bundle.isEmpty();
- }
-
- public Bundle getBundle() {
- return bundle;
- }
- }
-
- public static final class Parser {
-
- private final Bundle bundle;
-
- public Parser(@Nonnull Bundle bundle) {
- this.bundle = bundle;
- }
-
- @Nullable
- public String getCoverUri() {
- if (bundle.containsKey(KEY_PICTURE)) return bundle.getString(KEY_PICTURE);
- else return null;
- }
-
- @Nullable
- public Boolean isFavourite() {
- if (bundle.containsKey(KEY_FAV)) return bundle.getBoolean(KEY_FAV);
- else return null;
- }
- }
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/GroupItemBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/GroupItemBundle.kt
new file mode 100644
index 0000000000..458882343a
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/GroupItemBundle.kt
@@ -0,0 +1,18 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.os.Bundle
+import me.devsaki.hentoid.util.boolean
+import me.devsaki.hentoid.util.string
+
+/**
+ * Helper class to transfer payload data to [me.devsaki.hentoid.viewholders.GroupDisplayItem]
+ * through a Bundle
+ */
+class GroupItemBundle(val bundle: Bundle = Bundle()) {
+
+ var coverUri by bundle.string()
+
+ var isFavourite by bundle.boolean()
+
+ val isEmpty get() = bundle.isEmpty
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageItemBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageItemBundle.java
deleted file mode 100644
index bd0aa9e3be..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageItemBundle.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package me.devsaki.hentoid.activities.bundles;
-
-import android.os.Bundle;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-/**
- * Helper class to transfer payload data to {@link me.devsaki.hentoid.viewholders.ImageFileItem}
- * through a Bundle
- *
- * Use Builder class to set data; use Parser class to get data
- */
-public class ImageItemBundle {
- private static final String KEY_FAV_STATE = "favourite";
- private static final String KEY_CHP_ORDER = "chapterOrder";
-
- private ImageItemBundle() {
- throw new UnsupportedOperationException();
- }
-
- public static final class Builder {
-
- private final Bundle bundle = new Bundle();
-
- public void setIsFavourite(boolean value) {
- bundle.putBoolean(KEY_FAV_STATE, value);
- }
-
- public void setChapterOrder(int value) {
- bundle.putInt(KEY_CHP_ORDER, value);
- }
-
- public boolean isEmpty() {
- return bundle.isEmpty();
- }
-
- public Bundle getBundle() {
- return bundle;
- }
- }
-
- public static final class Parser {
-
- private final Bundle bundle;
-
- public Parser(@Nonnull Bundle bundle) {
- this.bundle = bundle;
- }
-
- @Nullable
- public Boolean isFavourite() {
- if (bundle.containsKey(KEY_FAV_STATE)) return bundle.getBoolean(KEY_FAV_STATE);
- else return null;
- }
-
- @Nullable
- public Integer getChapterOrder() {
- if (bundle.containsKey(KEY_CHP_ORDER)) return bundle.getInt(KEY_CHP_ORDER);
- else return null;
- }
- }
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageItemBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageItemBundle.kt
new file mode 100644
index 0000000000..016c967b90
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageItemBundle.kt
@@ -0,0 +1,18 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.os.Bundle
+import me.devsaki.hentoid.util.boolean
+import me.devsaki.hentoid.util.int
+
+/**
+ * Helper class to transfer payload data to [me.devsaki.hentoid.viewholders.ImageFileItem]
+ * through a Bundle
+ */
+class ImageItemBundle(val bundle: Bundle = Bundle()) {
+
+ var isFavourite by bundle.boolean()
+
+ var chapterOrder by bundle.int()
+
+ val isEmpty get() = bundle.isEmpty
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.java
deleted file mode 100644
index 7f83628237..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package me.devsaki.hentoid.activities.bundles;
-
-import android.os.Bundle;
-
-import javax.annotation.Nonnull;
-
-/**
- * Helper class to transfer data from any Activity to {@link me.devsaki.hentoid.activities.ImageViewerActivity}
- * through a Bundle
- *
- * Use Builder class to set data; use Parser class to get data
- */
-public class ImageViewerActivityBundle {
- private static final String KEY_CONTENT_ID = "contentId";
- private static final String KEY_SEARCH_PARAMS = "searchParams";
- private static final String KEY_IMAGE_INDEX = "imageIndex";
- private static final String KEY_IMAGE_NUMBER = "imageNumber";
- private static final String KEY_SCALE = "scale";
- private static final String KEY_FORCE_SHOW_GALLERY = "forceShowGallery";
-
- private ImageViewerActivityBundle() {
- throw new UnsupportedOperationException();
- }
-
- public static final class Builder {
-
- private final Bundle bundle = new Bundle();
-
- public void setContentId(long contentId) {
- bundle.putLong(KEY_CONTENT_ID, contentId);
- }
-
- public void setSearchParams(Bundle params) {
- bundle.putBundle(KEY_SEARCH_PARAMS, params);
- }
-
- public void setImageIndex(int imageIndex) {
- bundle.putInt(KEY_IMAGE_INDEX, imageIndex);
- }
-
- public void setPageNumber(int imageNumber) {
- bundle.putInt(KEY_IMAGE_NUMBER, imageNumber);
- }
-
- public void setScale(float scale) {
- bundle.putFloat(KEY_SCALE, scale);
- }
-
- public void setForceShowGallery(boolean value) {
- bundle.putBoolean(KEY_FORCE_SHOW_GALLERY, value);
- }
-
- public Bundle getBundle() {
- return bundle;
- }
- }
-
- public static final class Parser {
-
- private final Bundle bundle;
-
- public Parser(@Nonnull Bundle bundle) {
- this.bundle = bundle;
- }
-
- public long getContentId() {
- return bundle.getLong(KEY_CONTENT_ID, 0);
- }
-
- public Bundle getSearchParams() {
- return bundle.getBundle(KEY_SEARCH_PARAMS);
- }
-
- public int getImageIndex() {
- return bundle.getInt(KEY_IMAGE_INDEX, -1);
- }
-
- public int getPageNumber() {
- return bundle.getInt(KEY_IMAGE_NUMBER, -1);
- }
-
- public float getScale() {
- return bundle.getFloat(KEY_SCALE, -1);
- }
-
- public boolean isForceShowGallery() {
- return bundle.getBoolean(KEY_FORCE_SHOW_GALLERY, false);
- }
- }
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.kt
new file mode 100644
index 0000000000..3a8ec38826
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/ImageViewerActivityBundle.kt
@@ -0,0 +1,23 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.os.Bundle
+import me.devsaki.hentoid.util.*
+
+/**
+ * Helper class to transfer data from any Activity to [me.devsaki.hentoid.activities.ImageViewerActivity]
+ * through a Bundle
+ */
+class ImageViewerActivityBundle(val bundle: Bundle = Bundle()) {
+
+ var contentId by bundle.long(default = 0)
+
+ var searchParams by bundle.bundle()
+
+ var imageIndex by bundle.int(default = -1)
+
+ var pageNumber by bundle.int(default = -1)
+
+ var scale by bundle.float(default = -1f)
+
+ var isForceShowGallery by bundle.boolean(default = false)
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/LibraryBottomSortFilterBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/LibraryBottomSortFilterBundle.kt
new file mode 100644
index 0000000000..0de96a3234
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/LibraryBottomSortFilterBundle.kt
@@ -0,0 +1,18 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.os.Bundle
+import me.devsaki.hentoid.util.boolean
+import me.devsaki.hentoid.util.int
+
+/**
+ * Helper class to transfer data from any Activity to [me.devsaki.hentoid.fragments.library.LibraryBottomSortFilterFragment]
+ * through a Bundle
+ */
+class LibraryBottomSortFilterBundle(val bundle: Bundle = Bundle()) {
+
+ var isGroupsDisplayed by bundle.boolean(default = false)
+
+ var isUngroupedGroupDisplayed by bundle.boolean(default = false)
+
+ var showTabIndex by bundle.int(default = 0)
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/PrefsBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/PrefsBundle.kt
index 88660f0cfd..c535f05cb2 100644
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/PrefsBundle.kt
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/PrefsBundle.kt
@@ -4,12 +4,10 @@ import android.os.Bundle
import me.devsaki.hentoid.util.boolean
/**
- * Helper class to transfer data from any Activity to {@link me.devsaki.hentoid.activities.PrefsActivity}
+ * Helper class to transfer data from any Activity to [me.devsaki.hentoid.activities.PrefsActivity]
* through a Bundle
*/
-class PrefsBundle(private val bundle: Bundle) {
-
- constructor() : this(Bundle())
+class PrefsBundle(val bundle: Bundle = Bundle()) {
var isViewerPrefs by bundle.boolean(default = false)
@@ -18,6 +16,4 @@ class PrefsBundle(private val bundle: Bundle) {
var isDownloaderPrefs by bundle.boolean(default = false)
var isStoragePrefs by bundle.boolean(default = false)
-
- fun toBundle() = bundle
}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/QueueActivityBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/QueueActivityBundle.java
deleted file mode 100644
index b382a1aa2a..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/QueueActivityBundle.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package me.devsaki.hentoid.activities.bundles;
-
-import android.os.Bundle;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import me.devsaki.hentoid.enums.Site;
-
-/**
- * Helper class to transfer data from any Activity to {@link me.devsaki.hentoid.activities.QueueActivity}
- * through a Bundle
- *
- * Use Builder class to set data; use Parser class to get data
- */
-public class QueueActivityBundle {
- private static final String KEY_IS_ERROR = "isError";
- private static final String KEY_CONTENT_HASH = "contentHash";
- private static final String KEY_REVIVE_DOWNLOAD = "reviveDownload";
- private static final String KEY_REVIVE_OLD_COOKIE = "reviveOldCookie";
-
- private QueueActivityBundle() {
- throw new UnsupportedOperationException();
- }
-
- public static final class Builder {
-
- private final Bundle bundle = new Bundle();
-
- public void setIsErrorsTab(boolean isError) {
- bundle.putBoolean(KEY_IS_ERROR, isError);
- }
-
- public void setContentHash(long contentHash) {
- bundle.putLong(KEY_CONTENT_HASH, contentHash);
- }
-
- public void setReviveDownload(Site site) {
- bundle.putInt(KEY_REVIVE_DOWNLOAD, site.getCode());
- }
-
- public void setReviveOldCookie(String cookie) {
- bundle.putString(KEY_REVIVE_OLD_COOKIE, cookie);
- }
-
- public Bundle getBundle() {
- return bundle;
- }
- }
-
- public static final class Parser {
-
- private final Bundle bundle;
-
- public Parser(@Nonnull Bundle bundle) {
- this.bundle = bundle;
- }
-
- public boolean isErrorsTab() {
- return bundle.getBoolean(KEY_IS_ERROR, false);
- }
-
- public long contentHash() {
- return bundle.getLong(KEY_CONTENT_HASH, 0);
- }
-
- @Nullable
- public Site getRevivedSite() {
- int siteId = bundle.getInt(KEY_REVIVE_DOWNLOAD, -1);
- if (siteId > -1) return Site.searchByCode(siteId);
- else return null;
- }
-
- public String getOldCookie() {
- return bundle.getString(KEY_REVIVE_OLD_COOKIE, "");
- }
- }
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/QueueActivityBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/QueueActivityBundle.kt
new file mode 100644
index 0000000000..40a45c9d37
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/QueueActivityBundle.kt
@@ -0,0 +1,23 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.os.Bundle
+import me.devsaki.hentoid.enums.Site
+import me.devsaki.hentoid.util.boolean
+import me.devsaki.hentoid.util.int
+import me.devsaki.hentoid.util.long
+import me.devsaki.hentoid.util.string
+
+/**
+ * Helper class to transfer data from any Activity to [me.devsaki.hentoid.activities.QueueActivity]
+ * through a Bundle
+ */
+class QueueActivityBundle(val bundle: Bundle = Bundle()) {
+
+ var isErrorsTab by bundle.boolean(default = false)
+
+ var contentHash by bundle.long(default = 0)
+
+ var reviveDownloadForSiteCode by bundle.int(default = Site.NONE.code)
+
+ var reviveOldCookie by bundle.string(default = "")
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/SearchActivityBundle.java b/app/src/main/java/me/devsaki/hentoid/activities/bundles/SearchActivityBundle.java
deleted file mode 100644
index c0b5c569d3..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/activities/bundles/SearchActivityBundle.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package me.devsaki.hentoid.activities.bundles;
-
-import android.net.Uri;
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import me.devsaki.hentoid.database.domains.Attribute;
-import me.devsaki.hentoid.enums.AttributeType;
-import me.devsaki.hentoid.database.domains.AttributeMap;
-
-/**
- * Helper class to transfer data from any Activity to {@link me.devsaki.hentoid.activities.SearchActivity}
- * through a Bundle.
- *
- * Use Builder class to set data; use Parser class to get data
- */
-public class SearchActivityBundle {
- private static final String KEY_ATTRIBUTE_TYPES = "attributeTypes";
- private static final String KEY_MODE = "mode";
- private static final String KEY_URI = "uri";
- private static final String KEY_GROUP = "group";
- private static final String EXCLUDE_MODE = "exclude";
-
- private SearchActivityBundle() {
- throw new UnsupportedOperationException();
- }
-
- public static final class Builder {
-
- private final Bundle bundle = new Bundle();
-
- public void setAttributeTypes(AttributeType... attributeTypes) {
- ArrayList attrTypes = new ArrayList<>();
- for (AttributeType type : attributeTypes) attrTypes.add(type.getCode());
-
- bundle.putIntegerArrayList(KEY_ATTRIBUTE_TYPES, attrTypes);
- }
- public void setExcludeMode(boolean excludeMode){
- bundle.putBoolean(EXCLUDE_MODE, excludeMode);
- }
-
- public void setMode(int mode) {
- bundle.putInt(KEY_MODE, mode);
- }
-
- public void setGroup(long group) {
- bundle.putLong(KEY_GROUP, group);
- }
-
- public Builder setUri(Uri uri) {
- bundle.putString(KEY_URI, uri.toString());
- return this;
- }
-
- public static Uri buildSearchUri(List attributes) {
- AttributeMap metadataMap = new AttributeMap();
- metadataMap.addAll(attributes);
-
- Uri.Builder searchUri = new Uri.Builder()
- .scheme("search")
- .authority("hentoid");
-
-
- for (Map.Entry> entry : metadataMap.entrySet()) {
- AttributeType attrType = entry.getKey();
- List attrs = entry.getValue();
- if (attrs != null)
- for (Attribute attr : attrs)
- searchUri.appendQueryParameter(attrType.name(), attr.getId() + ";" + attr.getName() + ";" + attr.isExcluded());
- }
-
- return searchUri.build();
- }
-
- public Bundle getBundle() {
- return bundle;
- }
- }
-
- public static final class Parser {
-
- private final Bundle bundle;
-
- public Parser(@Nonnull Bundle bundle) {
- this.bundle = bundle;
- }
-
- public List getAttributeTypes() {
- List result = new ArrayList<>();
-
- List attrTypesList = bundle.getIntegerArrayList(KEY_ATTRIBUTE_TYPES);
- if (null != attrTypesList && !attrTypesList.isEmpty())
- for (Integer i : attrTypesList) result.add(AttributeType.searchByCode(i));
-
- return result;
- }
- public boolean getExcludeMode(){return bundle.getBoolean(EXCLUDE_MODE, false);}
- public int getMode() {
- return bundle.getInt(KEY_MODE, -1);
- }
-
- public long getGroupId() {
- return bundle.getLong(KEY_GROUP, -1);
- }
-
- @Nullable
- public Uri getUri() {
- Uri result = null;
-
- String uriStr = bundle.getString(KEY_URI, "");
- if (!uriStr.isEmpty()) result = Uri.parse(uriStr);
-
- return result;
- }
-
- public static List parseSearchUri(Uri uri) {
- List result = new ArrayList<>();
-
- if (uri != null)
- for (String typeStr : uri.getQueryParameterNames()) {
- AttributeType type = AttributeType.searchByName(typeStr);
- if (type != null)
- for (String attrStr : uri.getQueryParameters(typeStr)) {
- String[] attrParams = attrStr.split(";");
- if (3 == attrParams.length) {
- result.add(new Attribute(type, attrParams[1]).setId(Long.parseLong(attrParams[0])).setExcluded(Boolean.parseBoolean(attrParams[2])));
- }
- }
- }
-
- return result;
- }
- }
-
-
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/bundles/SearchActivityBundle.kt b/app/src/main/java/me/devsaki/hentoid/activities/bundles/SearchActivityBundle.kt
new file mode 100644
index 0000000000..fb1e73a795
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/activities/bundles/SearchActivityBundle.kt
@@ -0,0 +1,61 @@
+package me.devsaki.hentoid.activities.bundles
+
+import android.net.Uri
+import android.os.Bundle
+import me.devsaki.hentoid.database.domains.Attribute
+import me.devsaki.hentoid.database.domains.AttributeMap
+import me.devsaki.hentoid.enums.AttributeType
+import me.devsaki.hentoid.util.*
+
+/**
+ * Helper class to transfer data from any Activity to [me.devsaki.hentoid.activities.SearchActivity]
+ * through a Bundle
+ */
+class SearchActivityBundle(val bundle: Bundle = Bundle()) {
+
+ var attributeTypes by bundle.intArrayList()
+
+ var excludeMode by bundle.boolean(default = false)
+
+ var mode by bundle.int(default = -1)
+
+ var groupId by bundle.long(default = -1)
+
+ var uri by bundle.string(default = "")
+
+ // Helper methods
+ companion object {
+ fun buildSearchUri(attributes: List?): Uri {
+ val metadataMap = AttributeMap()
+ if (attributes != null) metadataMap.addAll(attributes)
+ val searchUri = Uri.Builder()
+ .scheme("search")
+ .authority("hentoid")
+ for ((attrType, attrs) in metadataMap) {
+ if (attrs != null) for (attr in attrs) searchUri.appendQueryParameter(
+ attrType.name,
+ attr.id.toString() + ";" + attr.name + ";" + attr.isExcluded
+ )
+ }
+ return searchUri.build()
+ }
+
+ fun parseSearchUri(uri: Uri?): List {
+ val result: MutableList = ArrayList()
+ if (uri != null) for (typeStr in uri.queryParameterNames) {
+ val type = AttributeType.searchByName(typeStr)
+ if (type != null) for (attrStr in uri.getQueryParameters(typeStr)) {
+ val attrParams = attrStr.split(";").toTypedArray()
+ if (3 == attrParams.size) {
+ result.add(
+ Attribute(type, attrParams[1])
+ .setId(attrParams[0].toLong())
+ .setExcluded(attrParams[2].toBoolean())
+ )
+ }
+ }
+ }
+ return result
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java
index 5f4e630a73..fafe8b2a63 100644
--- a/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java
+++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/BaseWebActivity.java
@@ -401,7 +401,7 @@ protected void onSaveInstanceState(@NonNull Bundle outState) {
// doesn't work that well (bugged when using back/forward commands). A valid solution still has to be found
BaseWebActivityBundle bundle = new BaseWebActivityBundle();
bundle.setUrl(webView.getUrl());
- outState.putAll(bundle.toBundle());
+ outState.putAll(bundle.getBundle());
}
@Override
@@ -934,6 +934,7 @@ void processDownload(boolean quickDownload, boolean isDownloadPlus) {
List errors = new ArrayList<>();
errors.add(new ErrorRecord(ErrorType.BLOCKED, currentContent.getUrl(), "tags", "blocked tags : " + TextUtils.join(", ", blockedTagsLocal), Instant.now()));
currentContent.setErrorLog(errors);
+ currentContent.setDownloadMode(Preferences.getBrowserDlAction());
currentContent.setStatus(StatusContent.ERROR);
dao.insertContent(currentContent);
ToastHelper.toast(R.string.blocked_tag_queued, blockedTagsLocal.get(0));
@@ -966,9 +967,9 @@ private void goToQueue() {
Intent intent = new Intent(this, QueueActivity.class);
if (currentContent != null) {
- QueueActivityBundle.Builder builder = new QueueActivityBundle.Builder();
+ QueueActivityBundle builder = new QueueActivityBundle();
builder.setContentHash(currentContent.uniqueHash());
- builder.setIsErrorsTab(currentContent.getStatus().equals(StatusContent.ERROR));
+ builder.setErrorsTab(currentContent.getStatus().equals(StatusContent.ERROR));
intent.putExtras(builder.getBundle());
}
@@ -1030,7 +1031,7 @@ int processContent(@NonNull Content onlineContent, boolean quickDownload) {
downloadParams.put(HttpHelper.HEADER_COOKIE_KEY, HttpHelper.getCookies(onlineContent.getCoverImageUrl()));
downloadParams.put(HttpHelper.HEADER_REFERER_KEY, onlineContent.getSite().getUrl());
- Response onlineCover = HttpHelper.getOnlineResource(
+ Response onlineCover = HttpHelper.getOnlineResourceFast(
HttpHelper.fixUrl(onlineContent.getCoverImageUrl(), getStartUrl()),
requestHeadersList,
getStartSite().useMobileAgent(),
@@ -1311,7 +1312,7 @@ private void onSettingsClick() {
PrefsBundle prefsBundle = new PrefsBundle();
prefsBundle.setBrowserPrefs(true);
- intent.putExtras(prefsBundle.toBundle());
+ intent.putExtras(prefsBundle.getBundle());
startActivity(intent);
}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java
index e04d6614cf..f9bfaf6329 100644
--- a/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java
+++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java
@@ -142,7 +142,7 @@ class CustomWebViewClient extends WebViewClient {
checkmark = ImageHelper.BitmapToWebp(
ImageHelper.tintBitmap(
- ImageHelper.getBitmapFromVectorDrawable(HentoidApp.getInstance(), R.drawable.ic_check),
+ ImageHelper.getBitmapFromVectorDrawable(HentoidApp.getInstance(), R.drawable.ic_checked),
HentoidApp.getInstance().getResources().getColor(R.color.secondary_light)
)
);
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java
index 1d23ddf047..c41943b0c5 100644
--- a/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java
+++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/HitomiActivity.java
@@ -7,11 +7,9 @@
import androidx.annotation.NonNull;
-import java.util.List;
import java.util.Map;
import me.devsaki.hentoid.database.domains.Content;
-import me.devsaki.hentoid.database.domains.ImageFile;
import me.devsaki.hentoid.enums.Site;
import me.devsaki.hentoid.enums.StatusContent;
import me.devsaki.hentoid.parsers.images.HitomiParser;
@@ -92,19 +90,20 @@ public WebResourceResponse shouldInterceptRequest(@NonNull WebView view, @NonNul
protected Content processContent(@NonNull Content content, @NonNull String url, boolean quickDownload) {
// Wait until the page's resources are all loaded
if (!quickDownload) {
- Timber.i(">> not loading");
+ Timber.v(">> not loading");
while (!isLoading()) Helper.pause(20);
- Timber.i(">> loading");
+ Timber.v(">> loading");
while (isLoading()) Helper.pause(100);
- Timber.i(">> done");
+ Timber.v(">> done");
}
HitomiParser parser = new HitomiParser();
try {
- List images = parser.parseImageListWithWebview(content, webView);
- content.setImageFiles(images);
+ /*List images =*/
+ parser.parseImageListWithWebview(content, webView); // Only fetch them when queue is processed
+ //content.setImageFiles(images);
content.setStatus(StatusContent.SAVED);
} catch (Exception e) {
- Timber.w(e);
+ Timber.i(e);
content.setStatus(StatusContent.IGNORED);
}
diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java
index baa7c540c2..bd3148a2b2 100644
--- a/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java
+++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/NhentaiActivity.java
@@ -15,7 +15,7 @@
public class NhentaiActivity extends BaseWebActivity {
private static final String DOMAIN_FILTER = "nhentai.net";
- private static final String[] GALLERY_FILTER = {"nhentai.net/g/", "nhentai.net/search/\\?q=[%0-9]+$"};
+ private static final String[] GALLERY_FILTER = {"nhentai.net/g/[%0-9]+[/]{0,1}$", "nhentai.net/search/\\?q=[%0-9]+$"};
private static final String[] RESULTS_FILTER = {"//nhentai.net[/]*$", "//nhentai.net/\\?", "//nhentai.net/search/\\?", "//nhentai.net/(character|artist|parody|tag|group)/"};
private static final String[] BLOCKED_CONTENT = {"popunder"};
private static final String[] AD_BLOCKS = {"section.advertisement"};
diff --git a/app/src/main/java/me/devsaki/hentoid/database/DatabaseMaintenance.java b/app/src/main/java/me/devsaki/hentoid/database/DatabaseMaintenance.java
index 395bb31282..9e7c0d24b7 100644
--- a/app/src/main/java/me/devsaki/hentoid/database/DatabaseMaintenance.java
+++ b/app/src/main/java/me/devsaki/hentoid/database/DatabaseMaintenance.java
@@ -29,6 +29,7 @@
import me.devsaki.hentoid.enums.AttributeType;
import me.devsaki.hentoid.enums.Grouping;
import me.devsaki.hentoid.enums.StatusContent;
+import me.devsaki.hentoid.util.ContentHelper;
import me.devsaki.hentoid.util.Preferences;
import timber.log.Timber;
@@ -305,6 +306,36 @@ private static void setDefaultPropertiesOneShot(@NonNull final Context context,
db.updateContentObject(c);
emitter.onNext(pos++ / max);
}
+ contents = db.selectContentWithNullDlCompletionDateField();
+ Timber.i("Set default value for Content.downloadCompletionDate field : %s items detected", contents.size());
+ max = contents.size();
+ pos = 1;
+ for (Content c : contents) {
+ if (ContentHelper.isInLibrary(c.getStatus()))
+ c.setDownloadCompletionDate(c.getDownloadDate());
+ else
+ c.setDownloadCompletionDate(0);
+ db.updateContentObject(c);
+ emitter.onNext(pos++ / max);
+ }
+ contents = db.selectContentWithInvalidUploadDate();
+ Timber.i("Fixing invalid upload dates : %s items detected", contents.size());
+ max = contents.size();
+ pos = 1;
+ for (Content c : contents) {
+ c.setUploadDate(c.getUploadDate() * 1000);
+ db.updateContentObject(c);
+ emitter.onNext(pos++ / max);
+ }
+ List chapters = db.selectChapterWithNullUploadDate();
+ Timber.i("Set default value for Chapter.uploadDate field : %s items detected", chapters.size());
+ max = chapters.size();
+ pos = 1;
+ for (Chapter c : chapters) {
+ c.setUploadDate(0);
+ emitter.onNext(pos++ / max);
+ }
+ db.insertChapters(chapters);
Timber.i("Set default ObjectBox properties : done");
} finally {
db.closeThreadResources();
@@ -441,7 +472,7 @@ private static void reattachGroupCovers(@NonNull final Context context, Observab
try {
// Compute missing downloaded Content size according to underlying ImageFile sizes
Timber.i("Reattaching group covers : start");
- List groups = db.selecGroupsWithNoCoverContent();
+ List groups = db.selectGroupsWithNoCoverContent();
Timber.i("Reattaching group covers : %s groups detected", groups.size());
int max = groups.size();
float pos = 1;
diff --git a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java
index 129db6f769..854df5d815 100644
--- a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java
+++ b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDAO.java
@@ -456,7 +456,7 @@ public LiveData> selectGroupsLive(
}
// Order by latest download date of children (ObjectBox can't do that natively)
- if (Preferences.Constant.ORDER_FIELD_DOWNLOAD_DATE == orderField) {
+ if (Preferences.Constant.ORDER_FIELD_DOWNLOAD_PROCESSING_DATE == orderField) {
MediatorLiveData> result = new MediatorLiveData<>();
result.addSource(workingData, groups -> {
int sortOrder = orderDesc ? -1 : 1;
diff --git a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java
index e352bf8b08..630762da2c 100644
--- a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java
+++ b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxDB.java
@@ -18,6 +18,7 @@
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -510,8 +511,10 @@ private Property getPropertyFromField(int prefsFieldCode) {
return Content_.author; // Might not be what users want when there are multiple authors
case Preferences.Constant.ORDER_FIELD_NB_PAGES:
return Content_.qtyPages;
- case Preferences.Constant.ORDER_FIELD_DOWNLOAD_DATE:
+ case Preferences.Constant.ORDER_FIELD_DOWNLOAD_PROCESSING_DATE:
return Content_.downloadDate;
+ case Preferences.Constant.ORDER_FIELD_DOWNLOAD_COMPLETION_DATE:
+ return Content_.downloadCompletionDate;
case Preferences.Constant.ORDER_FIELD_UPLOAD_DATE:
return Content_.uploadDate;
case Preferences.Constant.ORDER_FIELD_READ_DATE:
@@ -781,11 +784,11 @@ long[] selectContentUniversalByGroupItem(
return selectContentUniversalContentByGroupItem(queryStr, groupId, filterBookFavourites, contentAttrSubQuery.findIds(), orderField, orderDesc, bookCompletedOnly, bookNotCompletedOnly);
}
- public List getShuffledIds() {
+ List getShuffledIds() {
return Stream.of(store.boxFor(ShuffleRecord.class).getAll()).map(ShuffleRecord::getContentId).toList();
}
- public void shuffleContentIds() {
+ void shuffleContentIds() {
// Clear previous shuffled list
Box shuffleStore = store.boxFor(ShuffleRecord.class);
shuffleStore.removeAll();
@@ -796,19 +799,21 @@ public void shuffleContentIds() {
}
private long[] shuffleRandomSortId(Query query) {
- List queryIds = Helper.getListFromPrimitiveArray(query.findIds());
+ Set queryIds = Helper.getSetFromPrimitiveArray(query.findIds());
List shuffleIds = getShuffledIds();
+ LinkedHashSet shuffledSet = new LinkedHashSet<>(shuffleIds.size());
+ shuffledSet.addAll(shuffleIds);
// Keep common IDs
- shuffleIds.retainAll(queryIds);
+ shuffledSet.retainAll(queryIds);
// Isolate new IDs that have never been shuffled and append them at the end
- if (shuffleIds.size() < queryIds.size()) {
- queryIds.removeAll(shuffleIds);
- shuffleIds.addAll(queryIds);
+ if (shuffledSet.size() < queryIds.size()) {
+ queryIds.removeAll(shuffledSet);
+ shuffledSet.addAll(queryIds);
}
- return Helper.getPrimitiveArrayFromList(shuffleIds);
+ return Helper.getPrimitiveArrayFromList(Stream.of(shuffledSet).toList());
}
long[] selectContentSearchId(String title, long groupId, List tags, boolean filterBookFavourites, boolean filterPageFavourites, int orderField, boolean orderDesc, boolean bookCompletedOnly, boolean bookNotCompletedOnly) {
@@ -946,7 +951,7 @@ else if (bookNotCompletedOnly)
if (attr.isExcluded())
results.removeAll(idsAsList);
else
- results.retainAll(idsAsList);
+ results.retainAll(idsAsList); // Careful with retainAll performance
}
}
@@ -1515,8 +1520,8 @@ List selectDownloadedContentWithNoReadProgress() {
return store.boxFor(Content.class).query().in(Content_.status, libraryStatus).isNull(Content_.readProgress).build().find();
}
- List selecGroupsWithNoCoverContent() {
- return store.boxFor(Group.class).query().isNull(Group_.coverContentId).build().find();
+ List selectGroupsWithNoCoverContent() {
+ return store.boxFor(Group.class).query().isNull(Group_.coverContentId).or().equal(Group_.coverContentId, 0).build().find();
}
List selectContentWithNullCompleteField() {
@@ -1531,6 +1536,18 @@ List selectContentWithNullMergeField() {
return store.boxFor(Content.class).query().isNull(Content_.manuallyMerged).build().find();
}
+ List selectContentWithNullDlCompletionDateField() {
+ return store.boxFor(Content.class).query().isNull(Content_.downloadCompletionDate).build().find();
+ }
+
+ List selectContentWithInvalidUploadDate() {
+ return store.boxFor(Content.class).query().greater(Content_.uploadDate, 0).less(Content_.uploadDate, 10000000000L).build().find();
+ }
+
+ List selectChapterWithNullUploadDate() {
+ return store.boxFor(Chapter.class).query().isNull(Chapter_.uploadDate).build().find();
+ }
+
Query selectOldStoredContentQ() {
QueryBuilder query = store.boxFor(Content.class).query();
query.in(Content_.status, new int[]{
@@ -1550,8 +1567,10 @@ QueryBuilder selectStoredContentQ(boolean nonFavouritesOnly, boolean in
query.in(Content_.status, libraryQueueStatus);
else
query.in(Content_.status, libraryStatus);
+ /* TODO temp
query.notNull(Content_.storageUri);
query.notEqual(Content_.storageUri, "", QueryBuilder.StringOrder.CASE_INSENSITIVE);
+ */
if (nonFavouritesOnly) query.equal(Content_.favourite, false);
if (orderField > -1) {
Property field = getPropertyFromField(orderField);
@@ -1584,7 +1603,9 @@ Query selectNonHashedContent() {
long[] selectCustomGroupedContent() {
QueryBuilder customContentQB = store.boxFor(Content.class).query();
- customContentQB.link(Content_.groupItems).link(GroupItem_.group).equal(Group_.grouping, Grouping.CUSTOM.getId());
+ customContentQB.link(Content_.groupItems).link(GroupItem_.group)
+ .equal(Group_.grouping, Grouping.CUSTOM.getId()) // Custom group
+ .equal(Group_.subtype, 0); // Not the Ungrouped group (subtype 1)
return customContentQB.build().findIds();
// See https://github.com/objectbox/objectbox-java/issues/1028
/*
diff --git a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxRandomDataSource.java b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxRandomDataSource.java
index 881ef3a8c1..c5ebf6da91 100644
--- a/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxRandomDataSource.java
+++ b/app/src/main/java/me/devsaki/hentoid/database/ObjectBoxRandomDataSource.java
@@ -4,11 +4,15 @@
import androidx.paging.DataSource;
import androidx.paging.PositionalDataSource;
+import com.annimon.stream.Stream;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import io.objectbox.query.LazyList;
import io.objectbox.query.Query;
@@ -19,11 +23,29 @@
class ObjectBoxRandomDataSource extends PositionalDataSource {
private final Query query;
private final DataObserver> observer;
- private final List shuffleIds;
+ private final List shuffledList;
+ private final Map idsToQueryListIndexes = new HashMap<>();
private ObjectBoxRandomDataSource(Query query, List shuffleIds) {
this.query = query;
- this.shuffleIds = shuffleIds;
+
+ Set queryIds = Helper.getSetFromPrimitiveArray(query.findIds());
+ int idx = 0;
+ for (Long id : queryIds) idsToQueryListIndexes.put(id, idx++);
+
+ LinkedHashSet shuffledSet = new LinkedHashSet<>(shuffleIds.size());
+ shuffledSet.addAll(shuffleIds);
+
+ // Keep common IDs (intersect)
+ shuffledSet.retainAll(queryIds);
+
+ // Isolate new IDs that have never been shuffled and append them at the end
+ if (shuffledSet.size() < queryIds.size()) {
+ queryIds.removeAll(shuffledSet);
+ shuffledSet.addAll(queryIds);
+ }
+ shuffledList = Stream.of(shuffledSet).toList();
+
this.observer = data -> ObjectBoxRandomDataSource.this.invalidate();
query.subscribe().onlyChanges().weak().observer(this.observer);
}
@@ -49,31 +71,13 @@ public void loadRange(@NonNull PositionalDataSource.LoadRangeParams params, @Non
}
private List loadRange(int startPosition, int loadCount) {
- return shuffleRandomSort(this.query, startPosition, loadCount);
- }
-
- private List shuffleRandomSort(Query query, int startPosition, int loadCount) {
LazyList lazyList = query.findLazy();
- List queryIds = Helper.getListFromPrimitiveArray(query.findIds());
- Map idsToIndexes = new HashMap<>();
- for (int i = 0; i < queryIds.size(); i++) {
- idsToIndexes.put(queryIds.get(i), i);
- }
-
- // Keep common IDs
- shuffleIds.retainAll(queryIds);
-
- // Isolate new IDs that have never been shuffled and append them at the end
- if (shuffleIds.size() < queryIds.size()) {
- queryIds.removeAll(shuffleIds);
- shuffleIds.addAll(queryIds);
- }
- int maxPage = Math.min(startPosition + loadCount, shuffleIds.size());
+ int maxPage = Math.min(startPosition + loadCount, shuffledList.size());
List result = new ArrayList<>();
for (int i = startPosition; i < maxPage; i++) {
- Integer index = idsToIndexes.get(shuffleIds.get(i));
+ Integer index = idsToQueryListIndexes.get(shuffledList.get(i));
if (index != null)
result.add(lazyList.get(index));
}
diff --git a/app/src/main/java/me/devsaki/hentoid/database/domains/Chapter.java b/app/src/main/java/me/devsaki/hentoid/database/domains/Chapter.java
index 1b85d56021..ec3214c134 100644
--- a/app/src/main/java/me/devsaki/hentoid/database/domains/Chapter.java
+++ b/app/src/main/java/me/devsaki/hentoid/database/domains/Chapter.java
@@ -25,6 +25,7 @@ public class Chapter {
@Backlink(to = "chapter")
private ToMany imageFiles;
private String uniqueId = "";
+ private long uploadDate = 0;
public Chapter() { // Required by ObjectBox when an alternate constructor exists
@@ -132,6 +133,14 @@ public void addImageFile(ImageFile img) {
if (imageFiles != null) imageFiles.add(img);
}
+ public long getUploadDate() {
+ return uploadDate;
+ }
+
+ public void setUploadDate(long uploadDate) {
+ this.uploadDate = uploadDate;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/app/src/main/java/me/devsaki/hentoid/database/domains/Content.java b/app/src/main/java/me/devsaki/hentoid/database/domains/Content.java
index d6bb59060c..49b41f57c2 100644
--- a/app/src/main/java/me/devsaki/hentoid/database/domains/Content.java
+++ b/app/src/main/java/me/devsaki/hentoid/database/domains/Content.java
@@ -93,6 +93,7 @@ public class Content implements Serializable {
private Integer qtyPages; // Integer is actually unnecessary, but changing this to plain int requires a small DB model migration...
private long uploadDate;
private long downloadDate = 0;
+ private long downloadCompletionDate = 0;
@Index
@Convert(converter = StatusContent.StatusContentConverter.class, dbType = Integer.class)
private StatusContent status;
@@ -530,6 +531,15 @@ public Content setDownloadDate(long downloadDate) {
return this;
}
+ public long getDownloadCompletionDate() {
+ return downloadCompletionDate;
+ }
+
+ public Content setDownloadCompletionDate(long value) {
+ downloadCompletionDate = value;
+ return this;
+ }
+
public StatusContent getStatus() {
return status;
}
diff --git a/app/src/main/java/me/devsaki/hentoid/enums/Grouping.java b/app/src/main/java/me/devsaki/hentoid/enums/Grouping.java
index 92976f01b9..37c5700fd3 100644
--- a/app/src/main/java/me/devsaki/hentoid/enums/Grouping.java
+++ b/app/src/main/java/me/devsaki/hentoid/enums/Grouping.java
@@ -1,25 +1,28 @@
package me.devsaki.hentoid.enums;
-import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+import me.devsaki.hentoid.R;
/**
* Groupings
*/
public enum Grouping {
- FLAT(0, "Flat", false, false, false),
- ARTIST(1, "By artist", false, true, true),
- DL_DATE(2, "By download date", false, false, false),
- CUSTOM(98, "Custom", true, true, true),
- NONE(99, "None", false, false, false);
+ FLAT(0, R.string.groups_flat, false, false, false),
+ ARTIST(1, R.string.groups_by_artist, false, true, true),
+ DL_DATE(2, R.string.groups_by_dl_date, false, false, false),
+ CUSTOM(98, R.string.groups_custom, true, true, true),
+ NONE(99, R.string.none, false, false, false);
private final int id;
- private final String name;
+ private final @StringRes
+ int name;
private final boolean canReorderGroups;
private final boolean canDeleteGroups;
private final boolean canReorderBooks;
- Grouping(int id, @NonNull String name, boolean canReorderGroups, boolean canDeleteGroups, boolean canReorderBooks) {
+ Grouping(int id, @StringRes int name, boolean canReorderGroups, boolean canDeleteGroups, boolean canReorderBooks) {
this.id = id;
this.name = name;
this.canReorderGroups = canReorderGroups;
@@ -31,7 +34,8 @@ public int getId() {
return id;
}
- public String getName() {
+ public @StringRes
+ int getName() {
return name;
}
diff --git a/app/src/main/java/me/devsaki/hentoid/events/CommunicationEvent.java b/app/src/main/java/me/devsaki/hentoid/events/CommunicationEvent.java
index c1bf6a0f43..35e1132b48 100644
--- a/app/src/main/java/me/devsaki/hentoid/events/CommunicationEvent.java
+++ b/app/src/main/java/me/devsaki/hentoid/events/CommunicationEvent.java
@@ -6,7 +6,7 @@ public class CommunicationEvent {
public static final int EV_SEARCH = 1;
public static final int EV_ADVANCED_SEARCH = 2;
- public static final int EV_UPDATE_SORT = 4;
+ public static final int EV_UPDATE_TOOLBAR = 4;
public static final int EV_CLOSED = 5;
public static final int EV_ENABLE = 6;
public static final int EV_DISABLE = 7;
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/SearchBottomSheetFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/SearchBottomSheetFragment.java
index b4d4a784b0..fc31917709 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/SearchBottomSheetFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/SearchBottomSheetFragment.java
@@ -79,7 +79,7 @@ public class SearchBottomSheetFragment extends BottomSheetDialogFragment {
// Current page of paged content (used to display the attributes list as an endless list)
private int currentPage;
- private long mTotalSelectedCount; // Total count of current available attributes
+ private int mTotalSelectedCount; // Total count of current available attributes
// Selected attribute types (selection done in the activity view)
private List selectedAttributeTypes = new ArrayList<>();
@@ -98,9 +98,10 @@ public class SearchBottomSheetFragment extends BottomSheetDialogFragment {
private boolean excludeAttr = false;
public static void invoke(@NonNull Context context, @NonNull FragmentManager fragmentManager, AttributeType[] types, boolean excludeClicked) {
- SearchActivityBundle.Builder builder = new SearchActivityBundle.Builder();
+ SearchActivityBundle builder = new SearchActivityBundle();
- builder.setAttributeTypes(types);
+ ArrayList attrTypes = new ArrayList<>(Stream.of(types).map(AttributeType::getCode).toList());
+ builder.setAttributeTypes(attrTypes);
builder.setExcludeMode(excludeClicked);
SearchBottomSheetFragment searchBottomSheetFragment = new SearchBottomSheetFragment();
@@ -115,8 +116,10 @@ public void onAttach(@NonNull Context context) {
Bundle bundle = getArguments();
if (bundle != null) {
- SearchActivityBundle.Parser parser = new SearchActivityBundle.Parser(bundle);
- selectedAttributeTypes = parser.getAttributeTypes();
+ SearchActivityBundle parser = new SearchActivityBundle(bundle);
+ List attributeTypeCodes = parser.getAttributeTypes();
+ if (null == attributeTypeCodes) attributeTypeCodes = Collections.emptyList();
+ selectedAttributeTypes = Stream.of(attributeTypeCodes).map(AttributeType::searchByCode).toList();
excludeAttr = parser.getExcludeMode();
long groupId = parser.getGroupId();
currentPage = 1;
@@ -135,7 +138,7 @@ public void onAttach(@NonNull Context context) {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.include_search_filter_category, container, false);
+ View rootView = inflater.inflate(R.layout.include_search_bottom_panel, container, false);
AttributeType mainAttr = selectedAttributeTypes.get(0);
// Image that displays current metadata type icon (e.g. face icon for character)
@@ -257,7 +260,7 @@ private void onAttributesReady(CollectionDAO.AttributeQueryResult results) {
a.setName(LanguageHelper.getLocalNameFromLanguage(requireContext(), a.getName()));
}
- mTotalSelectedCount = results.totalSelectedAttributes/* - selectedAttributes.size()*/;
+ mTotalSelectedCount = (int) results.totalSelectedAttributes/* - selectedAttributes.size()*/;
if (clearOnSuccess) attributeAdapter.clear();
if (0 == mTotalSelectedCount) {
String searchQuery = tagSearchView.getQuery().toString();
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryBottomGroupsFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryBottomGroupsFragment.java
new file mode 100644
index 0000000000..10dcdb7e19
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryBottomGroupsFragment.java
@@ -0,0 +1,164 @@
+package me.devsaki.hentoid.fragments.library;
+
+import static me.devsaki.hentoid.util.Preferences.Constant.ARTIST_GROUP_VISIBILITY_ARTISTS;
+import static me.devsaki.hentoid.util.Preferences.Constant.ARTIST_GROUP_VISIBILITY_ARTISTS_GROUPS;
+import static me.devsaki.hentoid.util.Preferences.Constant.ARTIST_GROUP_VISIBILITY_GROUPS;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.mikepenz.fastadapter.FastAdapter;
+import com.mikepenz.fastadapter.adapters.ItemAdapter;
+import com.mikepenz.fastadapter.select.SelectExtension;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import me.devsaki.hentoid.R;
+import me.devsaki.hentoid.activities.bundles.LibraryBottomSortFilterBundle;
+import me.devsaki.hentoid.databinding.IncludeLibraryGroupsBottomPanelBinding;
+import me.devsaki.hentoid.enums.Grouping;
+import me.devsaki.hentoid.util.Preferences;
+import me.devsaki.hentoid.util.ThemeHelper;
+import me.devsaki.hentoid.viewholders.TextItem;
+import me.devsaki.hentoid.viewmodels.LibraryViewModel;
+import me.devsaki.hentoid.viewmodels.ViewModelFactory;
+
+public class LibraryBottomGroupsFragment extends BottomSheetDialogFragment {
+
+ private LibraryViewModel viewModel;
+
+ // UI
+ private IncludeLibraryGroupsBottomPanelBinding binding = null;
+
+ // RecyclerView controls
+ private final ItemAdapter> itemAdapter = new ItemAdapter<>();
+ private final FastAdapter> fastAdapter = FastAdapter.with(itemAdapter);
+ private SelectExtension> selectExtension;
+
+ // Variables
+ private boolean isCustomGroupingAvailable;
+
+ public static synchronized void invoke(
+ Context context,
+ FragmentManager fragmentManager) {
+ // Don't re-create it if already shown
+ for (Fragment fragment : fragmentManager.getFragments())
+ if (fragment instanceof LibraryBottomGroupsFragment) return;
+
+ LibraryBottomSortFilterBundle builder = new LibraryBottomSortFilterBundle();
+ LibraryBottomGroupsFragment libraryBottomSheetFragment = new LibraryBottomGroupsFragment();
+ libraryBottomSheetFragment.setArguments(builder.getBundle());
+ ThemeHelper.setStyle(context, libraryBottomSheetFragment, STYLE_NORMAL, R.style.Theme_Light_BottomSheetDialog);
+ libraryBottomSheetFragment.show(fragmentManager, "libraryBottomSheetFragment");
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+
+ Bundle bundle = getArguments();
+ if (bundle != null) {
+ LibraryBottomSortFilterBundle parser = new LibraryBottomSortFilterBundle(bundle);
+ }
+
+ ViewModelFactory vmFactory = new ViewModelFactory(requireActivity().getApplication());
+ viewModel = new ViewModelProvider(requireActivity(), vmFactory).get(LibraryViewModel.class);
+
+ viewModel.isCustomGroupingAvailable().observe(this, b -> {
+ isCustomGroupingAvailable = b;
+ itemAdapter.set(getGroupings());
+ });
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ binding = IncludeLibraryGroupsBottomPanelBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ // SORT TAB
+ // Gets (or creates and attaches if not yet existing) the extension from the given `FastAdapter`
+ selectExtension = fastAdapter.getOrCreateExtension(SelectExtension.class);
+ if (selectExtension != null) {
+ selectExtension.setSelectable(true);
+ selectExtension.setMultiSelect(false);
+ selectExtension.setSelectOnLongClick(false);
+ selectExtension.setSelectWithItemUpdate(true);
+ selectExtension.setAllowDeselection(false);
+ selectExtension.setSelectionListener((item, selected) -> {
+ if (selected) onSelectionChanged(item);
+ });
+ }
+ binding.list.setAdapter(fastAdapter);
+
+ updateArtistVisibility();
+
+ binding.artistDisplayGrp.addOnButtonCheckedListener((g, i, b) -> {
+ int code;
+ if (binding.artistDisplayArtists.isChecked() && binding.artistDisplayGroups.isChecked())
+ code = ARTIST_GROUP_VISIBILITY_ARTISTS_GROUPS;
+ else if (binding.artistDisplayArtists.isChecked())
+ code = ARTIST_GROUP_VISIBILITY_ARTISTS;
+ else code = ARTIST_GROUP_VISIBILITY_GROUPS;
+ Preferences.setArtistGroupVisibility(code);
+ updateArtistVisibility();
+ viewModel.searchGroup();
+ });
+ }
+
+ private List> getGroupings() {
+ List> result = new ArrayList<>();
+ result.add(createFromGrouping(Grouping.FLAT));
+ result.add(createFromGrouping(Grouping.ARTIST));
+ result.add(createFromGrouping(Grouping.DL_DATE));
+ if (isCustomGroupingAvailable) result.add(createFromGrouping(Grouping.CUSTOM));
+ return result;
+ }
+
+ private TextItem createFromGrouping(@NonNull Grouping grouping) {
+ return new TextItem<>(
+ getResources().getString(grouping.getName()),
+ grouping.getId(),
+ true,
+ Preferences.getGroupingDisplay().getId() == grouping.getId());
+ }
+
+ private void updateArtistVisibility() {
+ int visibility = (Preferences.getGroupingDisplay() == Grouping.ARTIST) ? View.VISIBLE : View.INVISIBLE;
+ binding.artistDisplayTxt.setVisibility(visibility);
+ binding.artistDisplayGrp.setVisibility(visibility);
+ int code = Preferences.getArtistGroupVisibility();
+ binding.artistDisplayArtists.setChecked(ARTIST_GROUP_VISIBILITY_ARTISTS_GROUPS == code || ARTIST_GROUP_VISIBILITY_ARTISTS == code);
+ binding.artistDisplayGroups.setChecked(ARTIST_GROUP_VISIBILITY_ARTISTS_GROUPS == code || ARTIST_GROUP_VISIBILITY_GROUPS == code);
+ }
+
+ /**
+ * Callback for any selected item
+ */
+ private void onSelectionChanged(TextItem item) {
+ Integer code = item.getTag();
+ if (code != null) {
+ viewModel.setGrouping(code);
+ updateArtistVisibility();
+ }
+ }
+}
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryBottomSortFilterFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryBottomSortFilterFragment.java
new file mode 100644
index 0000000000..50e7a7218e
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryBottomSortFilterFragment.java
@@ -0,0 +1,267 @@
+package me.devsaki.hentoid.fragments.library;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.annimon.stream.Optional;
+import com.annimon.stream.Stream;
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.mikepenz.fastadapter.FastAdapter;
+import com.mikepenz.fastadapter.adapters.ItemAdapter;
+import com.mikepenz.fastadapter.select.SelectExtension;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import me.devsaki.hentoid.R;
+import me.devsaki.hentoid.activities.LibraryActivity;
+import me.devsaki.hentoid.activities.bundles.LibraryBottomSortFilterBundle;
+import me.devsaki.hentoid.databinding.IncludeLibrarySortFilterBottomPanelBinding;
+import me.devsaki.hentoid.util.Preferences;
+import me.devsaki.hentoid.util.ThemeHelper;
+import me.devsaki.hentoid.viewholders.TextItem;
+import me.devsaki.hentoid.viewmodels.LibraryViewModel;
+import me.devsaki.hentoid.viewmodels.ViewModelFactory;
+import me.devsaki.hentoid.widget.ContentSearchManager;
+import me.devsaki.hentoid.widget.GroupSearchManager;
+
+public class LibraryBottomSortFilterFragment extends BottomSheetDialogFragment {
+
+ private LibraryViewModel viewModel;
+
+ // UI
+ private IncludeLibrarySortFilterBottomPanelBinding binding = null;
+
+ // RecyclerView controls
+ private final ItemAdapter> itemAdapter = new ItemAdapter<>();
+ private final FastAdapter> fastAdapter = FastAdapter.with(itemAdapter);
+ private SelectExtension> selectExtension;
+
+ // Variables
+ private boolean isUngroupedGroupDisplayed;
+ private boolean isGroupsDisplayed;
+ private boolean favouriteFilter;
+ private boolean completedFilter;
+ private boolean notCompletedFilter;
+ private @ColorInt
+ int greyColor;
+ private @ColorInt
+ int selectedColor;
+
+
+ public static synchronized void invoke(
+ Context context,
+ FragmentManager fragmentManager,
+ boolean isGroupsDisplayed,
+ boolean isUngroupedGroupDisplayed) {
+ // Don't re-create it if already shown
+ for (Fragment fragment : fragmentManager.getFragments())
+ if (fragment instanceof LibraryBottomSortFilterFragment) return;
+
+ LibraryBottomSortFilterBundle builder = new LibraryBottomSortFilterBundle();
+ builder.setGroupsDisplayed(isGroupsDisplayed);
+ builder.setUngroupedGroupDisplayed(isUngroupedGroupDisplayed);
+
+ LibraryBottomSortFilterFragment libraryBottomSheetFragment = new LibraryBottomSortFilterFragment();
+ libraryBottomSheetFragment.setArguments(builder.getBundle());
+ ThemeHelper.setStyle(context, libraryBottomSheetFragment, STYLE_NORMAL, R.style.Theme_Light_BottomSheetDialog);
+ libraryBottomSheetFragment.show(fragmentManager, "libraryBottomSheetFragment");
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+
+ Bundle bundle = getArguments();
+ if (bundle != null) {
+ LibraryBottomSortFilterBundle parser = new LibraryBottomSortFilterBundle(bundle);
+ isGroupsDisplayed = parser.isGroupsDisplayed();
+ isUngroupedGroupDisplayed = parser.isUngroupedGroupDisplayed();
+ }
+
+ ViewModelFactory vmFactory = new ViewModelFactory(requireActivity().getApplication());
+ viewModel = new ViewModelProvider(requireActivity(), vmFactory).get(LibraryViewModel.class);
+
+ viewModel.getContentSearchManagerBundle().observe(this, b -> {
+ if (isGroupsDisplayed) return;
+ ContentSearchManager.ContentSearchBundle searchBundle = new ContentSearchManager.ContentSearchBundle(b);
+ favouriteFilter = searchBundle.getFilterBookFavourites();
+ completedFilter = searchBundle.getFilterBookCompleted();
+ notCompletedFilter = searchBundle.getFilterBookNotCompleted();
+ updateFilterTab();
+ });
+ viewModel.getGroupSearchManagerBundle().observe(this, b -> {
+ if (!isGroupsDisplayed) return;
+ GroupSearchManager.GroupSearchBundle searchBundle = new GroupSearchManager.GroupSearchBundle(b);
+ favouriteFilter = searchBundle.getFilterFavourites();
+ updateFilterTab();
+ });
+
+ greyColor = ContextCompat.getColor(context, R.color.medium_gray);
+ selectedColor = ThemeHelper.getColor(context, R.color.secondary_light);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ binding = IncludeLibrarySortFilterBottomPanelBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ // SORT TAB
+ // Gets (or creates and attaches if not yet existing) the extension from the given `FastAdapter`
+ selectExtension = fastAdapter.getOrCreateExtension(SelectExtension.class);
+ if (selectExtension != null) {
+ selectExtension.setSelectable(true);
+ selectExtension.setMultiSelect(false);
+ selectExtension.setSelectOnLongClick(false);
+ selectExtension.setSelectWithItemUpdate(true);
+ selectExtension.setAllowDeselection(false);
+ selectExtension.setSelectionListener((item, selected) -> {
+ if (selected) this.onSelectionChanged();
+ });
+ }
+ binding.list.setAdapter(fastAdapter);
+ itemAdapter.set(getSortFields());
+
+ updateSortDirection();
+
+ binding.sortRandom.setOnClickListener(v -> {
+ viewModel.shuffleContent();
+ viewModel.searchContent();
+ });
+ binding.sortAscDesc.addOnButtonCheckedListener((g, i, b) -> {
+ if (!b) return;
+ if (isGroupsDisplayed) {
+ Preferences.setGroupSortDesc(i == R.id.sort_descending);
+ viewModel.searchGroup();
+ } else {
+ Preferences.setContentSortDesc(i == R.id.sort_descending);
+ viewModel.searchContent();
+ }
+ });
+
+ binding.filterFavsBtn.setOnClickListener(
+ v -> {
+ favouriteFilter = !favouriteFilter;
+ updateFilterTab();
+ if (isGroupsDisplayed)
+ viewModel.setGroupFavouriteFilter(favouriteFilter);
+ else
+ viewModel.setContentFavouriteFilter(favouriteFilter);
+ }
+ );
+ binding.filterCompletedBtn.setOnClickListener(
+ v -> {
+ completedFilter = !completedFilter;
+ updateFilterTab();
+ viewModel.toggleCompletedFilter();
+ }
+ );
+ binding.filterNotCompletedBtn.setOnClickListener(
+ v -> {
+ notCompletedFilter = !notCompletedFilter;
+ updateFilterTab();
+ viewModel.toggleNotCompletedFilter();
+ }
+ );
+ }
+
+ private void updateSortDirection() {
+ boolean isRandom = ((isGroupsDisplayed ? Preferences.getGroupSortField() : Preferences.getContentSortField()) == Preferences.Constant.ORDER_FIELD_RANDOM);
+ if (isRandom) {
+ binding.sortAscending.setVisibility(View.GONE);
+ binding.sortDescending.setVisibility(View.GONE);
+ binding.sortRandom.setVisibility(View.VISIBLE);
+ binding.sortRandom.setChecked(true);
+ } else {
+ binding.sortRandom.setVisibility(View.GONE);
+ binding.sortAscending.setVisibility(View.VISIBLE);
+ binding.sortDescending.setVisibility(View.VISIBLE);
+ boolean currentPrefSortDesc = isGroupsDisplayed ? Preferences.isGroupSortDesc() : Preferences.isContentSortDesc();
+ binding.sortAscDesc.check(currentPrefSortDesc ? R.id.sort_descending : R.id.sort_ascending);
+ }
+ }
+
+ private void updateFilterTab() {
+ binding.filterFavsBtn.setColorFilter(favouriteFilter ? selectedColor : greyColor);
+
+ int completeFiltersVisibility = isGroupsDisplayed ? View.GONE : View.VISIBLE;
+ binding.filterCompletedBtn.setVisibility(completeFiltersVisibility);
+ binding.filterNotCompletedBtn.setVisibility(completeFiltersVisibility);
+
+ binding.filterCompletedBtn.setColorFilter(completedFilter ? selectedColor : greyColor);
+ binding.filterNotCompletedBtn.setColorFilter(notCompletedFilter ? selectedColor : greyColor);
+ }
+
+ private List> getSortFields() {
+ List> result = new ArrayList<>();
+ if (isGroupsDisplayed) {
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_TITLE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_CHILDREN));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_DOWNLOAD_PROCESSING_DATE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_CUSTOM));
+ } else {
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_TITLE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_ARTIST));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_NB_PAGES));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_DOWNLOAD_PROCESSING_DATE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_DOWNLOAD_COMPLETION_DATE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_UPLOAD_DATE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_READ_DATE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_READS));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_SIZE));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_READ_PROGRESS));
+ if (Preferences.getGroupingDisplay().canReorderBooks() && !isUngroupedGroupDisplayed)
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_CUSTOM));
+ result.add(createFromFieldCode(Preferences.Constant.ORDER_FIELD_RANDOM));
+ }
+ return result;
+ }
+
+ private TextItem createFromFieldCode(int sortFieldCode) {
+ int currentPrefFieldCode = isGroupsDisplayed ? Preferences.getGroupSortField() : Preferences.getContentSortField();
+ return new TextItem<>(
+ getResources().getString(LibraryActivity.getNameFromFieldCode(sortFieldCode)),
+ sortFieldCode,
+ true,
+ currentPrefFieldCode == sortFieldCode);
+ }
+
+ /**
+ * Callback for any selection change (item added to or removed from selection)
+ */
+ private void onSelectionChanged() {
+ Optional> item = Stream.of(selectExtension.getSelectedItems()).findFirst();
+ if (item.isPresent()) {
+ Integer code = item.get().getTag();
+ if (code != null)
+ if (isGroupsDisplayed) {
+ Preferences.setGroupSortField(code);
+ viewModel.searchGroup();
+ } else {
+ Preferences.setContentSortField(code);
+ viewModel.searchContent();
+ }
+ }
+ updateSortDirection();
+ }
+}
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java
index b4293e9445..146e9a1f9d 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryContentFragment.java
@@ -6,7 +6,7 @@
import static me.devsaki.hentoid.events.CommunicationEvent.EV_DISABLE;
import static me.devsaki.hentoid.events.CommunicationEvent.EV_ENABLE;
import static me.devsaki.hentoid.events.CommunicationEvent.EV_SEARCH;
-import static me.devsaki.hentoid.events.CommunicationEvent.EV_UPDATE_SORT;
+import static me.devsaki.hentoid.events.CommunicationEvent.EV_UPDATE_TOOLBAR;
import static me.devsaki.hentoid.events.CommunicationEvent.RC_CONTENTS;
import static me.devsaki.hentoid.util.Preferences.Constant.QUEUE_NEW_DOWNLOADS_POSITION_ASK;
import static me.devsaki.hentoid.util.Preferences.Constant.QUEUE_NEW_DOWNLOADS_POSITION_BOTTOM;
@@ -22,12 +22,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
-import android.view.Gravity;
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 androidx.activity.OnBackPressedCallback;
@@ -35,11 +33,9 @@
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.DimenRes;
-import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-import androidx.appcompat.widget.PopupMenu;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
@@ -54,6 +50,7 @@
import com.annimon.stream.Stream;
import com.annimon.stream.function.Consumer;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
@@ -122,6 +119,7 @@
import me.devsaki.hentoid.widget.AutofitGridLayoutManager;
import me.devsaki.hentoid.widget.FastAdapterPreClickSelectHelper;
import me.devsaki.hentoid.widget.LibraryPager;
+import me.devsaki.hentoid.widget.ScrollPositionListener;
import me.zhanghai.android.fastscroll.FastScrollerBuilder;
import timber.log.Timber;
@@ -152,14 +150,10 @@ public class LibraryContentFragment extends Fragment implements ChangeGroupDialo
private RecyclerView recyclerView;
// LayoutManager of the recyclerView
private LinearLayoutManager llm;
-
- // === SORT TOOLBAR
- // Sort direction button
- private ImageView sortDirectionButton;
- // Sort reshuffle button
- private View sortReshuffleButton;
- // Sort field button
- private TextView sortFieldButton;
+ // "Go to top" FAB
+ private FloatingActionButton topFab;
+ // Scroll listener for the top FAB
+ private final ScrollPositionListener scrollListener = new ScrollPositionListener(this::onScrollPositionChange);
// === FASTADAPTER COMPONENTS AND HELPERS
private ItemAdapter itemAdapter;
@@ -185,6 +179,8 @@ public class LibraryContentFragment extends Fragment implements ChangeGroupDialo
private Group group = null;
// TODO doc
private boolean enabled = true;
+ // TODO doc
+ private Bundle contentSearchBundle = null;
// Used to start processing when the recyclerView has finished updating
private Debouncer listRefreshDebouncer;
@@ -215,13 +211,13 @@ public boolean areContentsTheSame(@NonNull Content oldItem, @NonNull Content new
@Nullable
@Override
public Object getChangePayload(@NonNull Content oldItem, @NonNull Content newItem) {
- ContentItemBundle.Builder diffBundleBuilder = new ContentItemBundle.Builder();
+ ContentItemBundle diffBundleBuilder = new ContentItemBundle();
if (oldItem.isFavourite() != newItem.isFavourite()) {
- diffBundleBuilder.setIsFavourite(newItem.isFavourite());
+ diffBundleBuilder.setFavourite(newItem.isFavourite());
}
if (oldItem.isCompleted() != newItem.isCompleted()) {
- diffBundleBuilder.setIsCompleted(newItem.isCompleted());
+ diffBundleBuilder.setCompleted(newItem.isCompleted());
}
if (oldItem.getReads() != newItem.getReads()) {
diffBundleBuilder.setReads(newItem.getReads());
@@ -261,13 +257,13 @@ public boolean areContentsTheSame(ContentItem oldContentItem, ContentItem newCon
if (null == oldItem || null == newItem) return false;
- ContentItemBundle.Builder diffBundleBuilder = new ContentItemBundle.Builder();
+ ContentItemBundle diffBundleBuilder = new ContentItemBundle();
if (oldItem.isFavourite() != newItem.isFavourite()) {
- diffBundleBuilder.setIsFavourite(newItem.isFavourite());
+ diffBundleBuilder.setFavourite(newItem.isFavourite());
}
if (oldItem.isCompleted() != newItem.isCompleted()) {
- diffBundleBuilder.setIsCompleted(newItem.isCompleted());
+ diffBundleBuilder.setCompleted(newItem.isCompleted());
}
if (oldItem.getReads() != newItem.getReads()) {
diffBundleBuilder.setReads(newItem.getReads());
@@ -324,12 +320,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- viewModel.getNewSearch().observe(getViewLifecycleOwner(), this::onNewSearch);
+ viewModel.getNewContentSearch().observe(getViewLifecycleOwner(), this::onNewSearch);
viewModel.getLibraryPaged().observe(getViewLifecycleOwner(), this::onLibraryChanged);
viewModel.getTotalContent().observe(getViewLifecycleOwner(), this::onTotalContentChanged);
viewModel.getGroup().observe(getViewLifecycleOwner(), this::onGroupChanged);
-
- viewModel.updateContentOrder(); // Trigger a blank search
+ viewModel.getContentSearchManagerBundle().observe(getViewLifecycleOwner(), b -> contentSearchBundle = b);
// Display pager tooltip
if (pager.isVisible()) pager.showTooltip(getViewLifecycleOwner());
@@ -353,10 +348,6 @@ public void onDisable() {
private void initUI(@NonNull View rootView) {
emptyText = requireViewById(rootView, R.id.library_empty_txt);
- sortDirectionButton = activity.get().getSortDirectionButton();
- sortReshuffleButton = activity.get().getSortReshuffleButton();
- sortFieldButton = activity.get().getSortFieldButton();
-
// RecyclerView
recyclerView = requireViewById(rootView, R.id.library_list);
if (Preferences.Constant.LIBRARY_DISPLAY_LIST == Preferences.getLibraryDisplay())
@@ -364,13 +355,22 @@ private void initUI(@NonNull View rootView) {
else
llm = new AutofitGridLayoutManager(requireContext(), (int) getResources().getDimension(R.dimen.card_grid_width));
recyclerView.setLayoutManager(llm);
+ recyclerView.addOnScrollListener(scrollListener);
new FastScrollerBuilder(recyclerView).build();
+ // Top FAB
+ topFab = requireViewById(rootView, R.id.top_fab);
+ topFab.setOnClickListener(v -> llm.scrollToPositionWithOffset(0, 0));
+ topFab.setOnLongClickListener(v -> {
+ Preferences.setTopFabEnabled(false);
+ topFab.setVisibility(View.GONE);
+ return true;
+ });
+
// Pager
pager.initUI(rootView);
setPagingMethod(Preferences.getEndlessScroll(), false);
- updateSortControls();
addCustomBackControl();
}
@@ -385,56 +385,6 @@ public void handleOnBackPressed() {
activity.get().getOnBackPressedDispatcher().addCallback(activity.get(), callback);
}
- private void updateSortControls() {
- // Sort controls
- sortDirectionButton.setImageResource(Preferences.isContentSortDesc() ? R.drawable.ic_simple_arrow_down : R.drawable.ic_simple_arrow_up);
- sortDirectionButton.setOnClickListener(v -> {
- boolean sortDesc = !Preferences.isContentSortDesc();
- Preferences.setContentSortDesc(sortDesc);
- // Update icon
- sortDirectionButton.setImageResource(sortDesc ? R.drawable.ic_simple_arrow_down : R.drawable.ic_simple_arrow_up);
- // Run a new search
- viewModel.updateContentOrder();
- activity.get().sortCommandsAutoHide(true, null);
- });
- sortReshuffleButton.setOnClickListener(v -> {
- viewModel.shuffleContent();
- viewModel.updateContentOrder();
- activity.get().sortCommandsAutoHide(true, null);
- });
- sortFieldButton.setText(getNameFromFieldCode(Preferences.getContentSortField()));
- sortFieldButton.setOnClickListener(v -> {
- // Load and display the field popup menu
- PopupMenu popup = new PopupMenu(requireContext(), sortFieldButton, Gravity.END);
- popup.getMenuInflater()
- .inflate(R.menu.library_books_sort_popup, popup.getMenu());
-
- popup.getMenu().findItem(R.id.sort_custom).setVisible(group != null && group.hasCustomBookOrder);
- popup.setOnMenuItemClickListener(item -> {
- // Update button text
- sortFieldButton.setText(item.getTitle());
- item.setChecked(true);
- int fieldCode = getFieldCodeFromMenuId(item.getItemId());
- if (fieldCode == Preferences.Constant.ORDER_FIELD_RANDOM) {
- viewModel.shuffleContent();
- sortDirectionButton.setVisibility(View.GONE);
- sortReshuffleButton.setVisibility(View.VISIBLE);
- } else {
- sortReshuffleButton.setVisibility(View.GONE);
- sortDirectionButton.setVisibility(View.VISIBLE);
- }
-
- Preferences.setContentSortField(fieldCode);
- // Run a new search
- viewModel.updateContentOrder();
- activity.get().sortCommandsAutoHide(true, popup);
- return true;
- });
- popup.show(); //showing popup menu
- activity.get().sortCommandsAutoHide(true, popup);
- }); //closing the setOnClickListener method
- }
-
private String getQuery() {
return activity.get().getQuery();
}
@@ -451,73 +401,10 @@ private void setMetadata(List attrs) {
activity.get().setMetadata(attrs);
}
- private int getFieldCodeFromMenuId(@IdRes int menuId) {
- switch (menuId) {
- case (R.id.sort_title):
- return Preferences.Constant.ORDER_FIELD_TITLE;
- case (R.id.sort_artist):
- return Preferences.Constant.ORDER_FIELD_ARTIST;
- case (R.id.sort_pages):
- return Preferences.Constant.ORDER_FIELD_NB_PAGES;
- case (R.id.sort_dl_date):
- return Preferences.Constant.ORDER_FIELD_DOWNLOAD_DATE;
- case (R.id.sort_read_date):
- return Preferences.Constant.ORDER_FIELD_READ_DATE;
- case (R.id.sort_reads):
- return Preferences.Constant.ORDER_FIELD_READS;
- case (R.id.sort_size):
- return Preferences.Constant.ORDER_FIELD_SIZE;
- case (R.id.sort_reading_progress):
- return Preferences.Constant.ORDER_FIELD_READ_PROGRESS;
- case (R.id.sort_custom):
- return Preferences.Constant.ORDER_FIELD_CUSTOM;
- case (R.id.sort_random):
- return Preferences.Constant.ORDER_FIELD_RANDOM;
- default:
- return Preferences.Constant.ORDER_FIELD_NONE;
- }
- }
+ private void enterEditMode() {
+ activity.get().setEditMode(true);
- private int getNameFromFieldCode(int prefFieldCode) {
- switch (prefFieldCode) {
- case (Preferences.Constant.ORDER_FIELD_TITLE):
- return R.string.sort_title;
- case (Preferences.Constant.ORDER_FIELD_ARTIST):
- return R.string.sort_artist;
- case (Preferences.Constant.ORDER_FIELD_NB_PAGES):
- return R.string.sort_pages;
- case (Preferences.Constant.ORDER_FIELD_DOWNLOAD_DATE):
- return R.string.sort_dl_date;
- case (Preferences.Constant.ORDER_FIELD_READ_DATE):
- return R.string.sort_read_date;
- case (Preferences.Constant.ORDER_FIELD_READS):
- return R.string.sort_reads;
- case (Preferences.Constant.ORDER_FIELD_SIZE):
- return R.string.sort_size;
- case (Preferences.Constant.ORDER_FIELD_READ_PROGRESS):
- return R.string.sort_reading_progress;
- case (Preferences.Constant.ORDER_FIELD_CUSTOM):
- return R.string.sort_custom;
- case (Preferences.Constant.ORDER_FIELD_RANDOM):
- return R.string.sort_random;
- default:
- return R.string.sort_invalid;
- }
- }
-
- private void toggleEditMode() {
- activity.get().toggleEditMode();
-
- // Leave edit mode by validating => Save new item position
- if (!activity.get().isEditMode()) {
- // Set ordering field to custom
- Preferences.setContentSortField(Preferences.Constant.ORDER_FIELD_CUSTOM);
- sortFieldButton.setText(getNameFromFieldCode(Preferences.Constant.ORDER_FIELD_CUSTOM));
- // Set ordering direction to ASC (we just manually ordered stuff; it has to be displayed as is)
- Preferences.setContentSortDesc(false);
- viewModel.saveContentPositions(Stream.of(itemAdapter.getAdapterItems()).map(ContentItem::getContent).withoutNulls().toList(), this::refreshIfNeeded);
- group.hasCustomBookOrder = true;
- } else if (group.hasCustomBookOrder) { // Enter edit mode -> warn if a custom order already exists
+ if (group.hasCustomBookOrder) { // Warn if a custom order already exists
new MaterialAlertDialogBuilder(requireContext(), ThemeHelper.getIdForCurrentTheme(requireContext(), R.style.Theme_Light_Dialog))
.setIcon(R.drawable.ic_warning)
.setTitle(R.string.app_name)
@@ -527,7 +414,7 @@ private void toggleEditMode() {
.setNegativeButton(R.string.no,
(dialog2, which) -> {
dialog2.dismiss();
- cancelEditMode();
+ cancelEdit();
})
.create()
.show();
@@ -536,18 +423,35 @@ private void toggleEditMode() {
setPagingMethod(Preferences.getEndlessScroll(), activity.get().isEditMode());
}
- private void cancelEditMode() {
+ private void cancelEdit() {
activity.get().setEditMode(false);
setPagingMethod(Preferences.getEndlessScroll(), false);
}
+ private void confirmEdit() {
+ activity.get().setEditMode(false);
+
+ // == Save new item position
+ // Set ordering field to custom
+ Preferences.setContentSortField(Preferences.Constant.ORDER_FIELD_CUSTOM);
+ // Set ordering direction to ASC (we just manually ordered stuff; it has to be displayed as is)
+ Preferences.setContentSortDesc(false);
+ viewModel.saveContentPositions(Stream.of(itemAdapter.getAdapterItems()).map(ContentItem::getContent).withoutNulls().toList(), this::refreshIfNeeded);
+ group.hasCustomBookOrder = true;
+
+ setPagingMethod(Preferences.getEndlessScroll(), activity.get().isEditMode());
+ }
+
private boolean onToolbarItemClicked(@NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.action_edit:
- toggleEditMode();
+ enterEditMode();
+ break;
+ case R.id.action_edit_confirm:
+ confirmEdit();
break;
case R.id.action_edit_cancel:
- cancelEditMode();
+ cancelEdit();
break;
default:
return activity.get().toolbarOnItemClicked(menuItem);
@@ -929,19 +833,18 @@ public void onAppUpdated(AppUpdatedEvent event) {
@Subscribe(threadMode = ThreadMode.MAIN)
public void onActivityEvent(CommunicationEvent event) {
- if (event.getRecipient() != RC_CONTENTS || null == sortDirectionButton) return;
+ if (event.getRecipient() != RC_CONTENTS) return;
switch (event.getType()) {
+ case EV_UPDATE_TOOLBAR:
+ addCustomBackControl();
+ activity.get().initFragmentToolbars(selectExtension, this::onToolbarItemClicked, this::onSelectionToolbarItemClicked);
+ break;
case EV_SEARCH:
if (event.getMessage() != null) onSubmitSearch(event.getMessage());
break;
case EV_ADVANCED_SEARCH:
onAdvancedSearchButtonClick();
break;
- case EV_UPDATE_SORT:
- updateSortControls();
- addCustomBackControl();
- activity.get().initFragmentToolbars(selectExtension, this::onToolbarItemClicked, this::onSelectionToolbarItemClicked);
- break;
case EV_ENABLE:
onEnable();
break;
@@ -981,11 +884,8 @@ private void customBackPress() {
new Handler(Looper.getMainLooper()).postDelayed(() -> activity.get().goBackToGroups(), 100);
}
// If none of the above and a search filter is on => clear search filter
- else if (isSearchQueryActive()) {
- setQuery("");
- setMetadata(Collections.emptyList());
- activity.get().hideSearchSortBar(false);
- viewModel.searchContent(getQuery(), getMetadata());
+ else if (activity.get().isFilterActive()) {
+ viewModel.clearContentFilters();
}
// If none of the above, user is asking to leave => use double-tap
else if (backButtonPressed + 2000 > SystemClock.elapsedRealtime()) {
@@ -1006,10 +906,13 @@ else if (backButtonPressed + 2000 > SystemClock.elapsedRealtime()) {
private void onSharedPreferenceChanged(String key) {
Timber.i("Prefs change detected : %s", key);
switch (key) {
+ case Preferences.Key.TOP_FAB:
+ topFab.setVisibility(Preferences.isTopFabEnabled() ? View.VISIBLE : View.GONE);
+ break;
case Preferences.Key.ENDLESS_SCROLL:
setPagingMethod(Preferences.getEndlessScroll(), activity.get().isEditMode());
FirebaseCrashlytics.getInstance().setCustomKey("Library display mode", Preferences.getEndlessScroll() ? "endless" : "paged");
- viewModel.updateContentOrder(); // Trigger a blank search
+ viewModel.searchContent(); // Trigger a blank search
break;
default:
// Nothing to handle there
@@ -1036,13 +939,13 @@ else if (s.equals(Site.NONE))
private void onAdvancedSearchButtonClick() {
Intent search = new Intent(this.getContext(), SearchActivity.class);
- SearchActivityBundle.Builder builder = new SearchActivityBundle.Builder();
+ SearchActivityBundle builder = new SearchActivityBundle();
if (!getMetadata().isEmpty())
- builder.setUri(SearchActivityBundle.Builder.buildSearchUri(getMetadata()));
+ builder.setUri(SearchActivityBundle.Companion.buildSearchUri(getMetadata()).toString());
if (group != null)
- builder.setGroup(group.id);
+ builder.setGroupId(group.id);
builder.setExcludeMode(excludeClicked);
search.putExtras(builder.getBundle());
@@ -1057,13 +960,13 @@ private void onAdvancedSearchButtonClick() {
private void advancedSearchReturnResult(final ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK
&& result.getData() != null && result.getData().getExtras() != null) {
- SearchActivityBundle.Parser parser = new SearchActivityBundle.Parser(result.getData().getExtras());
- Uri searchUri = parser.getUri();
+ SearchActivityBundle parser = new SearchActivityBundle(result.getData().getExtras());
+ Uri searchUri = Uri.parse(parser.getUri());
if (searchUri != null) {
excludeClicked = parser.getExcludeMode();
setQuery(searchUri.getPath());
- setMetadata(SearchActivityBundle.Parser.parseSearchUri(searchUri));
+ setMetadata(SearchActivityBundle.Companion.parseSearchUri(searchUri));
viewModel.searchContent(getQuery(), getMetadata());
}
}
@@ -1076,7 +979,7 @@ private void advancedSearchReturnResult(final ActivityResult result) {
*/
private void setPagingMethod(boolean isEndless, boolean isEditMode) {
// Editing will always be done in Endless mode
- viewModel.setPagingMethod(isEndless || isEditMode);
+ viewModel.setContentPagingMethod(isEndless || isEditMode);
// RecyclerView horizontal centering
ViewGroup.LayoutParams layoutParams = recyclerView.getLayoutParams();
@@ -1324,7 +1227,7 @@ private void onNewSearch(Boolean b) {
*/
private void onLibraryChanged(PagedList result) {
Timber.i(">> Library changed ! Size=%s", result.size());
- if (!enabled) return;
+ if (!enabled && !Preferences.getGroupingDisplay().equals(Grouping.FLAT)) return;
activity.get().updateTitle(result.size(), totalContentCount);
@@ -1423,7 +1326,7 @@ private boolean onItemClick(int position, @NonNull ContentItem item) {
// TODO doc
public void readBook(@NonNull Content content, boolean forceShowGallery) {
topItemPosition = getTopItemPosition();
- ContentHelper.openHentoidViewer(requireContext(), content, -1, viewModel.getSearchManagerBundle(), forceShowGallery);
+ ContentHelper.openHentoidViewer(requireContext(), content, -1, contentSearchBundle, forceShowGallery);
}
/**
@@ -1561,7 +1464,7 @@ public void onChangeGroupSuccess() {
*/
private void refreshIfNeeded() {
if (Grouping.CUSTOM.equals(Preferences.getGroupingDisplay()) || Preferences.getContentSortField() == Preferences.Constant.ORDER_FIELD_CUSTOM)
- viewModel.updateContentOrder();
+ viewModel.searchContent();
}
/**
@@ -1569,6 +1472,7 @@ private void refreshIfNeeded() {
* Activated when all _adapter_ items are placed on their definitive position
*/
private void differEndCallback() {
+ Timber.v(">> differEndCallback");
if (topItemPosition > -1) {
int targetPos = topItemPosition;
listRefreshDebouncer.submit(targetPos);
@@ -1654,4 +1558,18 @@ private void onDeleteSwipedBook(@NonNull final ContentItem item) {
if (content != null)
viewModel.deleteItems(Stream.of(content).toList(), Collections.emptyList(), false, null);
}
+
+ /**
+ * Scroll / page change listener
+ *
+ * @param scrollPosition New 0-based scroll position
+ */
+ private void onScrollPositionChange(int scrollPosition) {
+ if (Preferences.isTopFabEnabled()) {
+ if (scrollPosition > 2)
+ topFab.setVisibility(View.VISIBLE);
+ else
+ topFab.setVisibility(View.GONE);
+ }
+ }
}
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryGroupsFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryGroupsFragment.java
index da9bcdb022..29c7a5be1f 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryGroupsFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/library/LibraryGroupsFragment.java
@@ -4,7 +4,7 @@
import static me.devsaki.hentoid.events.CommunicationEvent.EV_DISABLE;
import static me.devsaki.hentoid.events.CommunicationEvent.EV_ENABLE;
import static me.devsaki.hentoid.events.CommunicationEvent.EV_SEARCH;
-import static me.devsaki.hentoid.events.CommunicationEvent.EV_UPDATE_SORT;
+import static me.devsaki.hentoid.events.CommunicationEvent.EV_UPDATE_TOOLBAR;
import static me.devsaki.hentoid.events.CommunicationEvent.RC_GROUPS;
import android.annotation.SuppressLint;
@@ -17,14 +17,11 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.TextView;
import androidx.activity.OnBackPressedCallback;
-import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.appcompat.widget.PopupMenu;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
@@ -111,12 +108,6 @@ public class LibraryGroupsFragment extends Fragment implements ItemTouchCallback
// LayoutManager of the recyclerView
private LinearLayoutManager llm;
- // === SORT TOOLBAR
- // Sort direction button
- private ImageView sortDirectionButton;
- // Sort field button
- private TextView sortFieldButton;
-
// === FASTADAPTER COMPONENTS AND HELPERS
private ItemAdapter itemAdapter;
private FastAdapter fastAdapter;
@@ -132,7 +123,7 @@ public class LibraryGroupsFragment extends Fragment implements ItemTouchCallback
// TODO doc
private boolean firstLibraryLoad = true;
// TODO doc
- private boolean enabled = true;
+ private boolean enabled = false;
public static final DiffCallback GROUPITEM_DIFF_CALLBACK = new DiffCallback() {
@@ -150,7 +141,7 @@ public boolean areContentsTheSame(GroupDisplayItem oldItem, GroupDisplayItem new
@Override
public @org.jetbrains.annotations.Nullable Object getChangePayload(GroupDisplayItem oldItem, int oldPos, GroupDisplayItem newItem, int newPos) {
- GroupItemBundle.Builder diffBundleBuilder = new GroupItemBundle.Builder();
+ GroupItemBundle diffBundleBuilder = new GroupItemBundle();
if (!newItem.getGroup().coverContent.isNull() && oldItem.getGroup().coverContent.getTargetId() != newItem.getGroup().coverContent.getTargetId()) {
diffBundleBuilder.setCoverUri(newItem.getGroup().coverContent.getTarget().getCover().getUsableUri());
@@ -205,12 +196,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
// Trigger a blank search
// TODO when group is reached from FLAT through the "group by" menu, this triggers a double-load and a screen blink
- viewModel.setGrouping(
- Preferences.getGroupingDisplay(),
- Preferences.getGroupSortField(),
- Preferences.isGroupSortDesc(),
- Preferences.getArtistGroupVisibility(),
- activity.get().isGroupFavsChecked());
+ viewModel.searchGroup();
}
public void onEnable() {
@@ -230,8 +216,6 @@ public void onDisable() {
*/
private void initUI(@NonNull View rootView) {
emptyText = requireViewById(rootView, R.id.library_empty_txt);
- sortDirectionButton = activity.get().getSortDirectionButton();
- sortFieldButton = activity.get().getSortFieldButton();
// RecyclerView
recyclerView = requireViewById(rootView, R.id.library_list);
@@ -245,7 +229,6 @@ private void initUI(@NonNull View rootView) {
// Pager
setPagingMethod();
- updateSortControls();
addCustomBackControl();
}
@@ -260,78 +243,16 @@ public void handleOnBackPressed() {
activity.get().getOnBackPressedDispatcher().addCallback(activity.get(), callback);
}
- private void updateSortControls() {
- // Sort controls
- sortDirectionButton.setImageResource(Preferences.isGroupSortDesc() ? R.drawable.ic_simple_arrow_down : R.drawable.ic_simple_arrow_up);
- sortDirectionButton.setOnClickListener(v -> {
- boolean sortDesc = !Preferences.isGroupSortDesc();
- Preferences.setGroupSortDesc(sortDesc);
- // Update icon
- sortDirectionButton.setImageResource(sortDesc ? R.drawable.ic_simple_arrow_down : R.drawable.ic_simple_arrow_up);
- // Run a new search
- viewModel.searchGroup(Preferences.getGroupingDisplay(), activity.get().getQuery(), Preferences.getGroupSortField(), sortDesc, Preferences.getArtistGroupVisibility(), activity.get().isGroupFavsChecked());
- activity.get().sortCommandsAutoHide(true, null);
- });
- sortFieldButton.setText(getNameFromFieldCode(Preferences.getGroupSortField()));
- sortFieldButton.setOnClickListener(v -> {
- // Load and display the field popup menu
- PopupMenu popup = new PopupMenu(requireContext(), sortDirectionButton);
- popup.getMenuInflater()
- .inflate(R.menu.library_groups_sort_popup, popup.getMenu());
- popup.getMenu().findItem(R.id.sort_custom).setVisible(Preferences.getGroupingDisplay().canReorderGroups());
- popup.setOnMenuItemClickListener(item -> {
- // Update button text
- sortFieldButton.setText(item.getTitle());
- item.setChecked(true);
- int fieldCode = getFieldCodeFromMenuId(item.getItemId());
- Preferences.setGroupSortField(fieldCode);
- // Run a new search
- viewModel.searchGroup(Preferences.getGroupingDisplay(), activity.get().getQuery(), fieldCode, Preferences.isGroupSortDesc(), Preferences.getArtistGroupVisibility(), activity.get().isGroupFavsChecked());
- activity.get().sortCommandsAutoHide(true, popup);
- return true;
- });
- popup.show(); //showing popup menu
- activity.get().sortCommandsAutoHide(true, popup);
- }); //closing the setOnClickListener method
- }
-
- private int getFieldCodeFromMenuId(@IdRes int menuId) {
- switch (menuId) {
- case (R.id.sort_title):
- return Preferences.Constant.ORDER_FIELD_TITLE;
- case (R.id.sort_books):
- return Preferences.Constant.ORDER_FIELD_CHILDREN;
- case (R.id.sort_dl_date):
- return Preferences.Constant.ORDER_FIELD_DOWNLOAD_DATE;
- case (R.id.sort_custom):
- return Preferences.Constant.ORDER_FIELD_CUSTOM;
- default:
- return Preferences.Constant.ORDER_FIELD_NONE;
- }
- }
-
- private int getNameFromFieldCode(int prefFieldCode) {
- switch (prefFieldCode) {
- case (Preferences.Constant.ORDER_FIELD_TITLE):
- return R.string.sort_title;
- case (Preferences.Constant.ORDER_FIELD_CHILDREN):
- return R.string.sort_books;
- case (Preferences.Constant.ORDER_FIELD_DOWNLOAD_DATE):
- return R.string.sort_dl_date;
- case (Preferences.Constant.ORDER_FIELD_CUSTOM):
- return R.string.sort_custom;
- default:
- return R.string.sort_invalid;
- }
- }
-
private boolean onToolbarItemClicked(@NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.action_edit:
- toggleEditMode();
+ enterEditMode();
+ break;
+ case R.id.action_edit_confirm:
+ confirmEdit();
break;
case R.id.action_edit_cancel:
- cancelEditMode();
+ cancelEdit();
break;
case R.id.action_group_new:
newGroupPrompt();
@@ -369,25 +290,29 @@ private boolean onSelectionToolbarItemClicked(@NonNull MenuItem menuItem) {
return true;
}
- private void toggleEditMode() {
- activity.get().toggleEditMode();
-
- // Leave edit mode by validating => Save new item position
- if (!activity.get().isEditMode()) {
- // Set ordering field to custom
- Preferences.setGroupSortField(Preferences.Constant.ORDER_FIELD_CUSTOM);
- sortFieldButton.setText(getNameFromFieldCode(Preferences.Constant.ORDER_FIELD_CUSTOM));
- // Set ordering direction to ASC (we just manually ordered stuff; it has to be displayed as is)
- Preferences.setGroupSortDesc(false);
- viewModel.saveGroupPositions(Stream.of(itemAdapter.getAdapterItems()).map(GroupDisplayItem::getGroup).withoutNulls().toList());
- }
+ private void enterEditMode() {
+ activity.get().setEditMode(true);
+ setPagingMethod();
+ viewModel.searchGroup();
+ }
+ private void cancelEdit() {
+ activity.get().setEditMode(false);
setPagingMethod();
}
- private void cancelEditMode() {
+ private void confirmEdit() {
activity.get().setEditMode(false);
+
+ // == Save new item position
+ // Set ordering field to custom
+ Preferences.setGroupSortField(Preferences.Constant.ORDER_FIELD_CUSTOM);
+ // Set ordering direction to ASC (we just manually ordered stuff; it has to be displayed as is)
+ Preferences.setGroupSortDesc(false);
+ viewModel.saveGroupPositions(Stream.of(itemAdapter.getAdapterItems()).map(GroupDisplayItem::getGroup).withoutNulls().toList());
+
setPagingMethod();
+ viewModel.searchGroup();
}
private void newGroupPrompt() {
@@ -488,7 +413,7 @@ private void deleteSelectedItems() {
PrefsBundle prefsBundle = new PrefsBundle();
prefsBundle.setStoragePrefs(true);
- intent.putExtras(prefsBundle.toBundle());
+ intent.putExtras(prefsBundle.getBundle());
requireContext().startActivity(intent);
});
@@ -571,14 +496,13 @@ public void onAppUpdated(AppUpdatedEvent event) {
public void onActivityEvent(CommunicationEvent event) {
if (event.getRecipient() != RC_GROUPS) return;
switch (event.getType()) {
- case EV_SEARCH:
- if (event.getMessage() != null) onSubmitSearch(event.getMessage());
- break;
- case EV_UPDATE_SORT:
- updateSortControls();
+ case EV_UPDATE_TOOLBAR:
addCustomBackControl();
activity.get().initFragmentToolbars(selectExtension, this::onToolbarItemClicked, this::onSelectionToolbarItemClicked);
break;
+ case EV_SEARCH:
+ if (event.getMessage() != null) onSubmitSearch(event.getMessage());
+ break;
case EV_ENABLE:
onEnable();
break;
@@ -608,14 +532,11 @@ private void customBackPress() {
if (!activity.get().collapseSearchMenu() && !activity.get().closeLeftDrawer()) {
// If none of the above and a search filter is on => clear search filter
- if (activity.get().isSearchQueryActive()) {
- activity.get().setQuery("");
- activity.get().setMetadata(Collections.emptyList());
- activity.get().hideSearchSortBar(false);
- viewModel.searchContent(activity.get().getQuery(), activity.get().getMetadata());
+ if (activity.get().isFilterActive()) {
+ viewModel.clearGroupFilters();
}
// If none of the above, user is asking to leave => use double-tap
- if (backButtonPressed + 2000 > SystemClock.elapsedRealtime()) {
+ else if (backButtonPressed + 2000 > SystemClock.elapsedRealtime()) {
callback.remove();
requireActivity().onBackPressed();
} else {
@@ -630,9 +551,7 @@ private void customBackPress() {
/**
* Initialize the paging method of the screen
*/
- private void setPagingMethod(/*boolean isEditMode*/) {
- viewModel.setPagingMethod(true);
-
+ private void setPagingMethod() {
itemAdapter = new ItemAdapter<>();
fastAdapter = FastAdapter.with(itemAdapter);
if (!fastAdapter.hasObservers()) fastAdapter.setHasStableIds(true);
@@ -708,6 +627,9 @@ private void onGroupsChanged(List result) {
List groups = Stream.of(result).map(g -> new GroupDisplayItem(g, touchHelper, viewType)).withoutNulls().distinct().toList();
FastAdapterDiffUtil.INSTANCE.set(itemAdapter, groups, GROUPITEM_DIFF_CALLBACK);
+ // Update visibility of search bar
+ activity.get().updateSearchBarOnResults(!result.isEmpty());
+
// Reset library load indicator
firstLibraryLoad = true;
}
@@ -736,12 +658,7 @@ private void onLibraryChanged(PagedList result) {
// Refresh groups (new content -> updated book count or new groups)
// TODO do we really want to do that, especially when deleting content ?
if (!firstLibraryLoad)
- viewModel.setGrouping(
- Preferences.getGroupingDisplay(),
- Preferences.getGroupSortField(),
- Preferences.isGroupSortDesc(),
- Preferences.getArtistGroupVisibility(),
- activity.get().isGroupFavsChecked());
+ viewModel.searchGroup();
else {
Timber.i(">>Library changed (groups) : ignored");
firstLibraryLoad = false;
@@ -759,12 +676,7 @@ else if (s.equals(Site.NONE))
else
ContentHelper.launchBrowserFor(requireContext(), query);
} else {
- viewModel.searchGroup(
- Preferences.getGroupingDisplay(),
- query, Preferences.getGroupSortField(),
- Preferences.isGroupSortDesc(),
- Preferences.getArtistGroupVisibility(),
- activity.get().isGroupFavsChecked());
+ viewModel.setGroupQuery(query);
}
}
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java
index 6fee465e16..2106f4f315 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/queue/QueueFragment.java
@@ -967,7 +967,7 @@ private void onSettingsClick() {
PrefsBundle prefsBundle = new PrefsBundle();
prefsBundle.setDownloaderPrefs(true);
- intent.putExtras(prefsBundle.toBundle());
+ intent.putExtras(prefsBundle.getBundle());
requireContext().startActivity(intent);
}
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt b/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt
index 8a2dd53f5f..118b11bfb9 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/tools/DuplicateDetailsFragment.kt
@@ -82,12 +82,12 @@ class DuplicateDetailsFragment : Fragment(R.layout.fragment_duplicate_details),
newItem: DuplicateItem,
newItemPosition: Int
): Any? {
- val diffBundleBuilder = DuplicateItemBundle.Builder()
+ val diffBundleBuilder = DuplicateItemBundle()
if (oldItem.keep != newItem.keep) {
- diffBundleBuilder.setKeep(newItem.keep)
+ diffBundleBuilder.isKeep = newItem.keep
}
if (oldItem.isBeingDeleted != newItem.isBeingDeleted) {
- diffBundleBuilder.setIsBeingDeleted(newItem.isBeingDeleted)
+ diffBundleBuilder.isBeingDeleted = newItem.isBeingDeleted
}
return if (diffBundleBuilder.isEmpty) null else diffBundleBuilder.bundle
}
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomContentFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomContentFragment.java
index 51394af45f..7a07fcb1ca 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomContentFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomContentFragment.java
@@ -30,7 +30,7 @@
import me.devsaki.hentoid.core.HentoidApp;
import me.devsaki.hentoid.database.domains.Content;
import me.devsaki.hentoid.database.domains.ImageFile;
-import me.devsaki.hentoid.databinding.IncludeViewerContentInfoBinding;
+import me.devsaki.hentoid.databinding.IncludeViewerContentBottomPanelBinding;
import me.devsaki.hentoid.util.ContentHelper;
import me.devsaki.hentoid.util.ThemeHelper;
import me.devsaki.hentoid.viewmodels.ImageViewerViewModel;
@@ -43,7 +43,7 @@ public class ViewerBottomContentFragment extends BottomSheetDialogFragment {
private ImageViewerViewModel viewModel;
// UI
- private IncludeViewerContentInfoBinding binding = null;
+ private IncludeViewerContentBottomPanelBinding binding = null;
static {
@@ -76,7 +76,7 @@ public void onAttach(@NonNull Context context) {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- binding = IncludeViewerContentInfoBinding.inflate(inflater, container, false);
+ binding = IncludeViewerContentBottomPanelBinding.inflate(inflater, container, false);
return binding.getRoot();
}
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomImageFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomImageFragment.java
index e3fcca2195..20cc2cea8c 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomImageFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerBottomImageFragment.java
@@ -58,10 +58,6 @@ public class ViewerBottomImageFragment extends BottomSheetDialogFragment {
private ImageViewerViewModel viewModel;
- private int imageIndex = -1;
- private float scale = -1;
- private ImageFile image = null;
-
// UI
private View rootView;
private ImageView imgThumb;
@@ -73,6 +69,11 @@ public class ViewerBottomImageFragment extends BottomSheetDialogFragment {
private ImageView shareButton;
private ImageView deleteButton;
+ // Variables
+ private int imageIndex = -1;
+ private float scale = -1;
+ private ImageFile image = null;
+
static {
Context context = HentoidApp.getInstance();
@@ -89,7 +90,7 @@ public class ViewerBottomImageFragment extends BottomSheetDialogFragment {
}
public static void invoke(Context context, FragmentManager fragmentManager, int imageIndex, float currentScale) {
- ImageViewerActivityBundle.Builder builder = new ImageViewerActivityBundle.Builder();
+ ImageViewerActivityBundle builder = new ImageViewerActivityBundle();
builder.setImageIndex(imageIndex);
builder.setScale(currentScale);
@@ -106,7 +107,7 @@ public void onAttach(@NonNull Context context) {
Bundle bundle = getArguments();
if (bundle != null) {
- ImageViewerActivityBundle.Parser parser = new ImageViewerActivityBundle.Parser(bundle);
+ ImageViewerActivityBundle parser = new ImageViewerActivityBundle(bundle);
imageIndex = parser.getImageIndex();
if (-1 == imageIndex) throw new IllegalArgumentException("Initialization failed");
scale = parser.getScale();
@@ -118,7 +119,7 @@ public void onAttach(@NonNull Context context) {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- rootView = inflater.inflate(R.layout.include_viewer_image_info, container, false);
+ rootView = inflater.inflate(R.layout.include_viewer_image_bottom_panel, container, false);
imgThumb = requireViewById(rootView, R.id.ivThumb);
imgPath = requireViewById(rootView, R.id.image_path);
@@ -263,7 +264,7 @@ private void onCopyClick() {
private void onShareClick() {
Uri fileUri = Uri.parse(image.getFileUri());
if (FileHelper.fileExists(requireContext(), fileUri))
- FileHelper.shareFile(requireContext(), fileUri, "Share picture");
+ FileHelper.shareFile(requireContext(), fileUri, "");
}
/**
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerGalleryFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerGalleryFragment.java
index e8a7ec7d64..cae4cffea5 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerGalleryFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerGalleryFragment.java
@@ -144,10 +144,10 @@ public boolean areContentsTheSame(ImageFileItem oldItem, ImageFileItem newItem)
if (null == oldImage || null == newImage) return false;
- ImageItemBundle.Builder diffBundleBuilder = new ImageItemBundle.Builder();
+ ImageItemBundle diffBundleBuilder = new ImageItemBundle();
if (oldImage.isFavourite() != newImage.isFavourite()) {
- diffBundleBuilder.setIsFavourite(newImage.isFavourite());
+ diffBundleBuilder.setFavourite(newImage.isFavourite());
}
if (oldItem.getChapterOrder() != newItem.getChapterOrder()) {
diffBundleBuilder.setChapterOrder(newItem.getChapterOrder());
diff --git a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPrefsDialogFragment.java b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPrefsDialogFragment.java
index 014ea143a0..d3646b43ef 100644
--- a/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPrefsDialogFragment.java
+++ b/app/src/main/java/me/devsaki/hentoid/fragments/viewer/ViewerPrefsDialogFragment.java
@@ -122,7 +122,7 @@ public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstance
PrefsBundle prefsBundle = new PrefsBundle();
prefsBundle.setViewerPrefs(true);
- intent.putExtras(prefsBundle.toBundle());
+ intent.putExtras(prefsBundle.getBundle());
requireContext().startActivity(intent);
});
diff --git a/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiGalleriesMetadata.java b/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiGalleriesMetadata.java
index 556d3c6414..b9adf99200 100644
--- a/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiGalleriesMetadata.java
+++ b/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiGalleriesMetadata.java
@@ -27,6 +27,7 @@ public static class EHentaiGalleryMetadata {
private String gid;
private String token;
+ private String posted;
private String title;
private String thumb;
private String filecount;
@@ -41,6 +42,8 @@ public Content update(@NonNull Content content, @Nonnull String url, @NonNull Si
.setTitle(title)
.setStatus(StatusContent.SAVED);
+ if (!posted.isEmpty()) content.setUploadDate(Long.parseLong(posted) * 1000);
+
if (updatePages) {
if (filecount != null) content.setQtyPages(Integer.parseInt(filecount));
else content.setQtyPages(0);
diff --git a/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiImageQuery.java b/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiImageQuery.java
index 26bb01ed1c..d779faf637 100644
--- a/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiImageQuery.java
+++ b/app/src/main/java/me/devsaki/hentoid/json/sources/EHentaiImageQuery.java
@@ -1,6 +1,6 @@
package me.devsaki.hentoid.json.sources;
-@SuppressWarnings({"unused, MismatchedQueryAndUpdateOfCollection", "squid:S1172", "squid:S1068"})
+@SuppressWarnings({"unused, MismatchedQueryAndUpdateOfCollection", "squid:S1172", "squid:S1068", "FieldCanBeLocal"})
public class EHentaiImageQuery {
private final String method = "imagedispatch";
private final Integer gid;
diff --git a/app/src/main/java/me/devsaki/hentoid/json/sources/HitomiGalleryInfo.java b/app/src/main/java/me/devsaki/hentoid/json/sources/HitomiGalleryInfo.java
index 3764f12eac..525002886b 100644
--- a/app/src/main/java/me/devsaki/hentoid/json/sources/HitomiGalleryInfo.java
+++ b/app/src/main/java/me/devsaki/hentoid/json/sources/HitomiGalleryInfo.java
@@ -9,6 +9,7 @@
import me.devsaki.hentoid.database.domains.Content;
import me.devsaki.hentoid.enums.AttributeType;
import me.devsaki.hentoid.enums.Site;
+import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.StringHelper;
@SuppressWarnings({"unused, MismatchedQueryAndUpdateOfCollection", "squid:S1172", "squid:S1068"})
@@ -19,7 +20,7 @@ public class HitomiGalleryInfo {
private String title;
private List characters;
private List groups;
- // private Date date; TODO
+ private String date; // Format : "YYYY-MM-DD HH:MM:SS-05" (-05 being the timezone of the server ?)
private String language;
private String language_localname;
private String language_url;
@@ -67,7 +68,8 @@ private void addAttribute(@NonNull AttributeType attributeType, @NonNull String
public void updateContent(@NonNull Content content) {
content.setTitle(StringHelper.removeNonPrintableChars(title));
-// content.setUploadDate(date.getTime()); TODO
+
+ content.setUploadDate(Helper.parseDatetimeToEpoch(date, "yyyy-MM-dd HH:mm:ssx"));
AttributeMap attributes = new AttributeMap();
if (parodys != null)
diff --git a/app/src/main/java/me/devsaki/hentoid/json/sources/LusciousBookMetadata.java b/app/src/main/java/me/devsaki/hentoid/json/sources/LusciousBookMetadata.java
index f3744a6f5b..43a52668d4 100644
--- a/app/src/main/java/me/devsaki/hentoid/json/sources/LusciousBookMetadata.java
+++ b/app/src/main/java/me/devsaki/hentoid/json/sources/LusciousBookMetadata.java
@@ -29,6 +29,7 @@ private static class AlbumInfo {
private String id;
private String title;
private String url;
+ private String created;
private Integer number_of_pictures;
private CoverInfo cover;
private LanguageInfo language;
@@ -59,6 +60,7 @@ public Content update(@NonNull Content content, boolean updateImages) {
return content.setStatus(StatusContent.IGNORED);
content.setUrl(info.url);
+ if (!info.created.isEmpty()) content.setUploadDate(Long.parseLong(info.created) * 1000);
content.setTitle(StringHelper.removeNonPrintableChars(info.title));
diff --git a/app/src/main/java/me/devsaki/hentoid/json/sources/PixivIllustMetadata.java b/app/src/main/java/me/devsaki/hentoid/json/sources/PixivIllustMetadata.java
index 2a22b29e17..4c5e13201a 100644
--- a/app/src/main/java/me/devsaki/hentoid/json/sources/PixivIllustMetadata.java
+++ b/app/src/main/java/me/devsaki/hentoid/json/sources/PixivIllustMetadata.java
@@ -303,7 +303,7 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setUrl(urlValue.replace(Site.PIXIV.getUrl(), ""));
content.setCoverImageUrl(illustData.getThumbUrl());
- content.setUploadDate(illustData.getUploadTimestamp());
+ content.setUploadDate(illustData.getUploadTimestamp() * 1000);
content.putAttributes(getAttributes());
diff --git a/app/src/main/java/me/devsaki/hentoid/json/sources/YoastGalleryMetadata.java b/app/src/main/java/me/devsaki/hentoid/json/sources/YoastGalleryMetadata.java
new file mode 100644
index 0000000000..9824eac854
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/json/sources/YoastGalleryMetadata.java
@@ -0,0 +1,28 @@
+package me.devsaki.hentoid.json.sources;
+
+import com.squareup.moshi.Json;
+
+import java.util.List;
+
+@SuppressWarnings({"unused, MismatchedQueryAndUpdateOfCollection", "squid:S1172", "squid:S1068"})
+public class YoastGalleryMetadata {
+ private @Json(name = "@graph")
+ List graph;
+
+ private static class GraphData {
+ private @Json(name = "@type")
+ String type;
+ private String datePublished;
+ }
+
+ public String getDatePublished() {
+ if (graph != null) {
+ for (GraphData data : graph) {
+ if (data.type != null && data.type.equalsIgnoreCase("webpage")) {
+ return data.datePublished;
+ }
+ }
+ }
+ return "";
+ }
+}
diff --git a/app/src/main/java/me/devsaki/hentoid/notification/action/UserActionNotification.kt b/app/src/main/java/me/devsaki/hentoid/notification/action/UserActionNotification.kt
index 330109ffce..28ad75a79d 100644
--- a/app/src/main/java/me/devsaki/hentoid/notification/action/UserActionNotification.kt
+++ b/app/src/main/java/me/devsaki/hentoid/notification/action/UserActionNotification.kt
@@ -29,9 +29,9 @@ class UserActionNotification(val site: Site, private val oldCookie: String) : No
val resultIntent = Intent(context, QueueActivity::class.java)
resultIntent.flags =
Intent.FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_NEW_TASK// or Intent.FLAG_ACTIVITY_SINGLE_TOP
- val bundleBuilder = QueueActivityBundle.Builder()
- bundleBuilder.setReviveDownload(site)
- bundleBuilder.setReviveOldCookie(oldCookie)
+ val bundleBuilder = QueueActivityBundle()
+ bundleBuilder.reviveDownloadForSiteCode = site.code
+ bundleBuilder.reviveOldCookie = oldCookie
resultIntent.putExtras(bundleBuilder.bundle)
val flags =
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/ParseHelper.java b/app/src/main/java/me/devsaki/hentoid/parsers/ParseHelper.java
index 4b43248708..bb2a012c26 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/ParseHelper.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/ParseHelper.java
@@ -9,6 +9,8 @@
import com.annimon.stream.Optional;
import com.annimon.stream.Stream;
+import org.apache.commons.lang3.tuple.ImmutableTriple;
+import org.apache.commons.lang3.tuple.Triple;
import org.greenrobot.eventbus.EventBus;
import org.jsoup.nodes.Element;
@@ -33,6 +35,7 @@
import me.devsaki.hentoid.enums.StatusContent;
import me.devsaki.hentoid.events.DownloadPreparationEvent;
import me.devsaki.hentoid.util.ContentHelper;
+import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.StringHelper;
import me.devsaki.hentoid.util.network.HttpHelper;
@@ -373,28 +376,45 @@ public static String getExtensionFromFormat(@NonNull Map imgForm
* @return Chapters detected from the given list of links, associated with the given Content ID
*/
public static List getChaptersFromLinks(@NonNull List chapterLinks, long contentId) {
+ return getChaptersFromLinks(chapterLinks, contentId, null, null);
+ }
+
+ public static List getChaptersFromLinks(
+ @NonNull List chapterLinks,
+ long contentId,
+ String dateCssQuery,
+ String datePattern) {
List result = new ArrayList<>();
Set urls = new HashSet<>();
// First extract data and filter URL duplicates
- List> chapterData = new ArrayList<>();
+ List> chapterData = new ArrayList<>();
for (Element e : chapterLinks) {
String url = e.attr("href").trim();
String name = e.attr("title").trim();
if (name.isEmpty())
name = StringHelper.removeNonPrintableChars(e.ownText()).trim();
+ long epoch = 0;
+ if (dateCssQuery != null && !dateCssQuery.isEmpty()) {
+ Element dateElement = e.selectFirst(dateCssQuery);
+ if (dateElement != null) {
+ String[] dateStr = dateElement.text().split("-");
+ if (dateStr.length > 1) epoch = Helper.parseDateToEpoch(dateStr[1], datePattern);
+ }
+ }
// Make sure we're not adding duplicates
if (!urls.contains(url)) {
urls.add(url);
- chapterData.add(new Pair<>(url, name));
+ chapterData.add(new ImmutableTriple<>(url, name, epoch));
}
}
Collections.reverse(chapterData); // Put unique results in their chronological order
int order = 0;
// Build the final list
- for (Pair chapter : chapterData) {
- Chapter chp = new Chapter(order++, chapter.first, chapter.second);
+ for (Triple chapter : chapterData) {
+ Chapter chp = new Chapter(order++, chapter.getLeft(), chapter.getMiddle());
+ chp.setUploadDate(chapter.getRight());
chp.setContentId(contentId);
result.add(chp);
}
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/AllPornComicContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/AllPornComicContent.java
index afbe499acf..3c8797ba8f 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/AllPornComicContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/AllPornComicContent.java
@@ -4,6 +4,7 @@
import org.jsoup.nodes.Element;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -14,15 +15,21 @@
import me.devsaki.hentoid.enums.AttributeType;
import me.devsaki.hentoid.enums.Site;
import me.devsaki.hentoid.enums.StatusContent;
+import me.devsaki.hentoid.json.sources.YoastGalleryMetadata;
import me.devsaki.hentoid.parsers.ParseHelper;
+import me.devsaki.hentoid.util.Helper;
+import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
+import timber.log.Timber;
public class AllPornComicContent extends BaseContentParser {
@Selector(value = "head [property=og:image]", attr = "content", defValue = "")
private String coverUrl;
@Selector(value = "head [property=og:title]", attr = "content", defValue = "")
private String title;
+ @Selector(value = "head script.yoast-schema-graph")
+ private Element metadata;
@Selector(value = ".post-content a[href*='characters']")
private List characterTags;
@@ -45,6 +52,17 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setTitle(StringHelper.removeNonPrintableChars(title));
} else content.setTitle(NO_TITLE);
+ if (metadata != null && metadata.childNodeSize() > 0) {
+ try {
+ YoastGalleryMetadata galleryMeta = JsonHelper.jsonToObject(metadata.childNode(0).toString(), YoastGalleryMetadata.class);
+ String publishDate = galleryMeta.getDatePublished(); // e.g. 2021-01-27T15:20:38+00:00
+ if (!publishDate.isEmpty())
+ content.setUploadDate(Helper.parseDatetimeToEpoch(publishDate, "yyyy-MM-dd'T'HH:mm:ssXXX"));
+ } catch (IOException e) {
+ Timber.i(e);
+ }
+ }
+
AttributeMap attributes = new AttributeMap();
ParseHelper.parseAttributes(attributes, AttributeType.CHARACTER, characterTags, false, Site.ALLPORNCOMIC);
ParseHelper.parseAttributes(attributes, AttributeType.SERIE, seriesTags, false, Site.ALLPORNCOMIC);
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/DoujinsContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/DoujinsContent.java
index b82a645d33..8373f71952 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/DoujinsContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/DoujinsContent.java
@@ -16,6 +16,7 @@
import me.devsaki.hentoid.enums.StatusContent;
import me.devsaki.hentoid.parsers.ParseHelper;
import me.devsaki.hentoid.parsers.images.DoujinsParser;
+import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
@@ -28,6 +29,8 @@ public class DoujinsContent extends BaseContentParser {
private List artists;
@Selector(value = "a[href*='/searches?tag_id=']") // To deduplicate
private List tags;
+ @Selector(value = "#content .folder-message")
+ private List contentInfo;
public Content update(@NonNull final Content content, @Nonnull String url, boolean updateImages) {
@@ -41,6 +44,16 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setTitle(StringHelper.removeNonPrintableChars(e.text()));
}
+ if (contentInfo != null && !contentInfo.isEmpty()) {
+ for (Element e : contentInfo) {
+ if (e.text().toLowerCase().contains("•")) { // e.g. March 16th, 2022 • 25 images
+ String[] parts = e.text().split("•");
+ content.setUploadDate(Helper.parseDateToEpoch(parts[0], "MMMM dd',' yyyy"));
+ break;
+ }
+ }
+ }
+
if (images != null && !images.isEmpty()) {
// Cover = thumb from the 1st page
String coverUrl = images.get(0).attr("data-thumb2");
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/ManhwaContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/ManhwaContent.java
index 447bf1e1d9..a3bc646bb7 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/ManhwaContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/ManhwaContent.java
@@ -4,6 +4,7 @@
import org.jsoup.nodes.Element;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -14,15 +15,21 @@
import me.devsaki.hentoid.enums.AttributeType;
import me.devsaki.hentoid.enums.Site;
import me.devsaki.hentoid.enums.StatusContent;
+import me.devsaki.hentoid.json.sources.YoastGalleryMetadata;
import me.devsaki.hentoid.parsers.ParseHelper;
+import me.devsaki.hentoid.util.Helper;
+import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
+import timber.log.Timber;
public class ManhwaContent extends BaseContentParser {
@Selector(value = "head [property=og:image]", attr = "content")
private String coverUrl;
@Selector(value = ".breadcrumb a")
private List breadcrumbs;
+ @Selector(value = "head script.yoast-schema-graph")
+ private Element metadata;
@Selector(value = ".author-content a")
private List author;
@Selector(value = ".artist-content a")
@@ -42,6 +49,17 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setTitle(title);
content.populateUniqueSiteId();
+ if (metadata != null && metadata.childNodeSize() > 0) {
+ try {
+ YoastGalleryMetadata galleryMeta = JsonHelper.jsonToObject(metadata.childNode(0).toString(), YoastGalleryMetadata.class);
+ String publishDate = galleryMeta.getDatePublished(); // e.g. 2021-01-27T15:20:38+00:00
+ if (!publishDate.isEmpty())
+ content.setUploadDate(Helper.parseDatetimeToEpoch(publishDate, "yyyy-MM-dd'T'HH:mm:ssXXX"));
+ } catch (IOException e) {
+ Timber.i(e);
+ }
+ }
+
AttributeMap attributes = new AttributeMap();
ParseHelper.parseAttributes(attributes, AttributeType.ARTIST, artist, false, Site.MANHWA);
ParseHelper.parseAttributes(attributes, AttributeType.ARTIST, author, false, Site.MANHWA);
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/MrmContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/MrmContent.java
index fed8ace185..6800abfc52 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/MrmContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/MrmContent.java
@@ -16,12 +16,15 @@
import me.devsaki.hentoid.enums.Site;
import me.devsaki.hentoid.enums.StatusContent;
import me.devsaki.hentoid.parsers.ParseHelper;
+import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
public class MrmContent extends BaseContentParser {
@Selector(value = "article h1", defValue = "")
private String title;
+ @Selector(value = "time.entry-time", attr = "datetime", defValue = "")
+ private String uploadDate;
@Selector(".entry-header .entry-meta .entry-categories a")
private List categories;
@Selector(value = ".entry-header .entry-terms a[href*='/lang/']")
@@ -44,6 +47,8 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setTitle(title);
} else content.setTitle(NO_TITLE);
+ content.setUploadDate(Helper.parseDatetimeToEpoch(uploadDate,"yyyy-MM-dd'T'HH:mm:ssXXX")); // e.g. 2022-03-20T00:09:43+07:00
+
if (images != null && !images.isEmpty())
content.setCoverImageUrl(ParseHelper.getImgSrc(images.get(0)));
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/NhentaiContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/NhentaiContent.java
index d0679b4bce..52a090f277 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/NhentaiContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/NhentaiContent.java
@@ -16,6 +16,7 @@
import me.devsaki.hentoid.enums.StatusContent;
import me.devsaki.hentoid.parsers.ParseHelper;
import me.devsaki.hentoid.parsers.images.NhentaiParser;
+import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
@@ -31,6 +32,8 @@ public class NhentaiContent extends BaseContentParser {
// Fallback value for title (see #449)
@Selector(value = "#info h1", defValue = NO_TITLE)
private String titleAlt;
+ @Selector(value = "#tags time", attr = "datetime", defValue = "")
+ private String uploadDate;
@Selector(value = "#info a[href*='/artist']")
private List artists;
@@ -68,6 +71,8 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
if (titleDef.isEmpty()) titleDef = titleAlt.trim();
content.setTitle(StringHelper.removeNonPrintableChars(titleDef));
+ content.setUploadDate(Helper.parseDatetimeToEpoch(uploadDate,"yyyy-MM-dd'T'HH:mm:ss'.'nnnnnnXXX")); // e.g. 2022-03-20T00:09:43.309901+00:00
+
AttributeMap attributes = new AttributeMap();
ParseHelper.parseAttributes(attributes, AttributeType.ARTIST, artists, false, "name", Site.NHENTAI);
ParseHelper.parseAttributes(attributes, AttributeType.CIRCLE, circles, false, "name", Site.NHENTAI);
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/PorncomixContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/PorncomixContent.java
index 6d6474ee4c..60d87e5355 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/PorncomixContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/PorncomixContent.java
@@ -4,6 +4,7 @@
import org.jsoup.nodes.Element;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -15,15 +16,21 @@
import me.devsaki.hentoid.enums.AttributeType;
import me.devsaki.hentoid.enums.Site;
import me.devsaki.hentoid.enums.StatusContent;
+import me.devsaki.hentoid.json.sources.YoastGalleryMetadata;
import me.devsaki.hentoid.parsers.ParseHelper;
+import me.devsaki.hentoid.util.Helper;
+import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
+import timber.log.Timber;
public class PorncomixContent extends BaseContentParser {
@Selector(value = "head [property=og:image]", attr = "content", defValue = "")
private String coverUrl;
@Selector(value = "head [property=og:title]", attr = "content", defValue = "")
private String title;
+ @Selector(value = "head script.yoast-schema-graph")
+ private Element metadata;
@Selector(value = ".wp-manga-tags-list a[href*='tag']")
private List mangaTags;
@@ -63,6 +70,17 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setUrl(url);
content.setCoverImageUrl(coverUrl);
+ if (metadata != null && metadata.childNodeSize() > 0) {
+ try {
+ YoastGalleryMetadata galleryMeta = JsonHelper.jsonToObject(metadata.childNode(0).toString(), YoastGalleryMetadata.class);
+ String publishDate = galleryMeta.getDatePublished(); // e.g. 2021-01-27T15:20:38+00:00
+ if (!publishDate.isEmpty())
+ content.setUploadDate(Helper.parseDatetimeToEpoch(publishDate, "yyyy-MM-dd'T'HH:mm:ssXXX"));
+ } catch (IOException e) {
+ Timber.i(e);
+ }
+ }
+
String artist = "";
if (content.getUrl().contains("/manga")) {
String[] titleParts = title.split("-");
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/ToonilyContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/ToonilyContent.java
index aabe3bf848..a29e9c98d6 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/ToonilyContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/ToonilyContent.java
@@ -4,6 +4,7 @@
import org.jsoup.nodes.Element;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
@@ -14,15 +15,21 @@
import me.devsaki.hentoid.enums.AttributeType;
import me.devsaki.hentoid.enums.Site;
import me.devsaki.hentoid.enums.StatusContent;
+import me.devsaki.hentoid.json.sources.YoastGalleryMetadata;
import me.devsaki.hentoid.parsers.ParseHelper;
+import me.devsaki.hentoid.util.Helper;
+import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
+import timber.log.Timber;
public class ToonilyContent extends BaseContentParser {
@Selector(value = "head [property=og:image]", attr = "content")
private String coverUrl;
@Selector(value = ".breadcrumb a")
private List breadcrumbs;
+ @Selector(value = "head script.yoast-schema-graph")
+ private Element metadata;
@Selector(value = ".author-content a")
private List author;
@Selector(value = ".artist-content a")
@@ -42,6 +49,17 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setTitle(title);
content.populateUniqueSiteId();
+ if (metadata != null && metadata.childNodeSize() > 0) {
+ try {
+ YoastGalleryMetadata galleryMeta = JsonHelper.jsonToObject(metadata.childNode(0).toString(), YoastGalleryMetadata.class);
+ String publishDate = galleryMeta.getDatePublished(); // e.g. 2021-01-27T15:20:38+00:00
+ if (!publishDate.isEmpty())
+ content.setUploadDate(Helper.parseDatetimeToEpoch(publishDate, "yyyy-MM-dd'T'HH:mm:ssXXX"));
+ } catch (IOException e) {
+ Timber.i(e);
+ }
+ }
+
AttributeMap attributes = new AttributeMap();
ParseHelper.parseAttributes(attributes, AttributeType.ARTIST, artist, false, Site.TOONILY);
ParseHelper.parseAttributes(attributes, AttributeType.ARTIST, author, false, Site.TOONILY);
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/content/TsuminoContent.java b/app/src/main/java/me/devsaki/hentoid/parsers/content/TsuminoContent.java
index 5ab68dbcc5..12e818d8fb 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/content/TsuminoContent.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/content/TsuminoContent.java
@@ -16,6 +16,7 @@
import me.devsaki.hentoid.enums.AttributeType;
import me.devsaki.hentoid.enums.StatusContent;
import me.devsaki.hentoid.parsers.ParseHelper;
+import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.StringHelper;
import pl.droidsonroids.jspoon.annotation.Selector;
@@ -26,6 +27,8 @@ public class TsuminoContent extends BaseContentParser {
private Element cover;
@Selector(value = "div#Title", defValue = "")
private String title;
+ @Selector(value = "div#Uploaded", defValue = "")
+ private String uploadDate;
@Selector(value = "div#Pages", defValue = "")
private String pages;
@Selector(value = "div#Artist a")
@@ -53,6 +56,8 @@ public Content update(@NonNull final Content content, @Nonnull String url, boole
content.setCoverImageUrl(coverUrl);
content.setTitle(StringHelper.removeNonPrintableChars(title));
+ content.setUploadDate(Helper.parseDateToEpoch(uploadDate, "yyyy MMMM dd")); // e.g. 2021 December 13
+
AttributeMap attributes = new AttributeMap();
ParseHelper.parseAttributes(attributes, AttributeType.ARTIST, artists, false, TSUMINO);
ParseHelper.parseAttributes(attributes, AttributeType.CIRCLE, circles, false, TSUMINO);
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java b/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java
index 9b78822e35..a87daddc3b 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/images/EHentaiParser.java
@@ -36,7 +36,6 @@
import me.devsaki.hentoid.json.sources.EHentaiImageQuery;
import me.devsaki.hentoid.json.sources.EHentaiImageResponse;
import me.devsaki.hentoid.parsers.ParseHelper;
-import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.exception.EmptyResultException;
import me.devsaki.hentoid.util.exception.LimitReachedException;
@@ -51,14 +50,34 @@ public class EHentaiParser implements ImageListParser {
private final ParseProgress progress = new ParseProgress();
- private boolean processHalted = false;
-
static class MpvInfo {
Integer gid;
String mpvkey;
String api_url;
List images;
Integer pagecount;
+
+ MpvImageInfo getImageInfo(int index) {
+ return new MpvImageInfo(this, images.get(index), index + 1);
+ }
+ }
+
+ static class MpvImageInfo {
+ final int gid;
+ final int pageNum;
+ final String mpvkey;
+ final String api_url;
+ final EHentaiImageMetadata image;
+ final int pagecount;
+
+ public MpvImageInfo(MpvInfo info, EHentaiImageMetadata img, int pageNum) {
+ gid = info.gid;
+ this.pageNum = pageNum;
+ mpvkey = info.mpvkey;
+ api_url = info.api_url;
+ image = img;
+ pagecount = info.pagecount;
+ }
}
@@ -98,38 +117,53 @@ public List parseImageList(@NonNull Content content) throws Exception
Document galleryDoc = getOnlineDocument(content.getGalleryUrl(), headers, useHentoidAgent, useWebviewAgent);
if (galleryDoc != null) {
// Detect if multipage viewer is on
-// result = loadMpv("https://e-hentai.org/mpv/530350/8b3c7e4a21/", headers, useHentoidAgent);
+ //result = loadMpv("https://e-hentai.org/mpv/530350/8b3c7e4a21/", headers, useHentoidAgent, useWebviewAgent);
Elements elements = galleryDoc.select(MPV_LINK_CSS);
if (!elements.isEmpty()) {
String mpvUrl = elements.get(0).attr("href");
try {
- result = loadMpv(content, mpvUrl, headers, useHentoidAgent, useWebviewAgent);
+ result = loadMpv(mpvUrl, headers, useHentoidAgent, useWebviewAgent, progress);
} catch (EmptyResultException e) {
- result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent);
+ result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent, progress);
}
} else {
- result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent);
+ result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent, progress);
}
}
-
-
progress.complete();
// If the process has been halted manually, the result is incomplete and should not be returned as is
- if (processHalted) throw new PreparationInterruptedException();
+ if (progress.isProcessHalted()) throw new PreparationInterruptedException();
} finally {
EventBus.getDefault().unregister(this);
}
return result;
}
- @SuppressWarnings("BusyWait")
- private List loadMpv(
- @NonNull Content content,
+ private static EHentaiImageResponse getMpvImage(
+ @NonNull MpvImageInfo imageInfo,
+ @NonNull final List> headers,
+ boolean useHentoidAgent,
+ boolean useWebviewAgent) throws EmptyResultException, IOException {
+ EHentaiImageQuery query = new EHentaiImageQuery(imageInfo.gid, imageInfo.image.getKey(), imageInfo.mpvkey, imageInfo.pageNum);
+ String jsonRequest = JsonHelper.serializeToJson(query, EHentaiImageQuery.class);
+ Response response = HttpHelper.postOnlineResource(imageInfo.api_url, headers, true, useHentoidAgent, useWebviewAgent, jsonRequest, JsonHelper.JSON_MIME_TYPE);
+ ResponseBody body = response.body();
+ if (null == body)
+ throw new EmptyResultException("API " + imageInfo.api_url + " returned an empty body");
+ String bodyStr = body.string();
+ if (!bodyStr.contains("{") || !bodyStr.contains("}"))
+ throw new EmptyResultException("API " + imageInfo.api_url + " returned non-JSON data");
+
+ return JsonHelper.jsonToObject(bodyStr, EHentaiImageResponse.class);
+ }
+
+ static List loadMpv(
@NonNull final String mpvUrl,
@NonNull final List> headers,
boolean useHentoidAgent,
- boolean useWebviewAgent) throws IOException, EmptyResultException {
+ boolean useWebviewAgent,
+ @NonNull ParseProgress progress) throws IOException, EmptyResultException {
List result = new ArrayList<>();
// B.1- Open the MPV and parse gallery metadata
@@ -138,38 +172,30 @@ private List loadMpv(
throw new EmptyResultException("No exploitable data has been found on the multiple page viewer");
int pageCount = Math.min(mpvInfo.pagecount, mpvInfo.images.size());
- progress.start(content.getId(), -1, pageCount);
-
- // B.2- Call the API to get the pictures URL
- for (int pageNum = 1; pageNum <= pageCount && !processHalted; pageNum++) {
- EHentaiImageQuery query = new EHentaiImageQuery(mpvInfo.gid, mpvInfo.images.get(pageNum - 1).getKey(), mpvInfo.mpvkey, pageNum);
- String jsonRequest = JsonHelper.serializeToJson(query, EHentaiImageQuery.class);
- Response response = HttpHelper.postOnlineResource(mpvInfo.api_url, headers, true, useHentoidAgent, useWebviewAgent, jsonRequest, JsonHelper.JSON_MIME_TYPE);
- ResponseBody body = response.body();
- if (null == body)
- throw new EmptyResultException("API " + mpvInfo.api_url + " returned an empty body");
- String bodyStr = body.string();
- if (!bodyStr.contains("{") || !bodyStr.contains("}"))
- throw new EmptyResultException("API " + mpvInfo.api_url + " returned non-JSON data");
-
- EHentaiImageResponse imageMetadata = JsonHelper.jsonToObject(bodyStr, EHentaiImageResponse.class);
- if (1 == pageNum)
+
+ for (int pageNum = 1; pageNum <= pageCount && !progress.isProcessHalted(); pageNum++) {
+ // Get the URL of he 1st page as the cover
+ if (1 == pageNum) {
+ EHentaiImageResponse imageMetadata = getMpvImage(mpvInfo.getImageInfo(0), headers, useHentoidAgent, useWebviewAgent);
result.add(ImageFile.newCover(imageMetadata.getUrl(), StatusContent.SAVED));
- result.add(ParseHelper.urlToImageFile(imageMetadata.getUrl(), pageNum, pageCount, StatusContent.SAVED));
- progress.advance();
- // Emulate JS loader
- if (0 == pageNum % 10) Helper.pause(750);
+ }
+ // Add page URLs to be read later by the downloader
+ result.add(ImageFile.fromPageUrl(
+ pageNum,
+ JsonHelper.serializeToJson(mpvInfo.getImageInfo(pageNum - 1), MpvImageInfo.class),
+ StatusContent.SAVED, pageCount));
}
return result;
}
- private List loadClassic(
+ static List loadClassic(
@NonNull Content content,
@NonNull final Document galleryDoc,
@NonNull final List> headers,
boolean useHentoidAgent,
- boolean useWebviewAgent) throws IOException {
+ boolean useWebviewAgent,
+ @NonNull ParseProgress progress) throws IOException {
List result = new ArrayList<>();
// A.1- Detect the number of pages of the gallery
@@ -187,7 +213,7 @@ private List loadClassic(
fetchPageUrls(galleryDoc, pageUrls);
if (nbGalleryPages > 1) {
- for (int i = 1; i < nbGalleryPages && !processHalted; i++) {
+ for (int i = 1; i < nbGalleryPages && !progress.isProcessHalted(); i++) {
Document pageDoc = getOnlineDocument(content.getGalleryUrl() + "/?p=" + i, headers, useHentoidAgent, useWebviewAgent);
if (pageDoc != null) fetchPageUrls(pageDoc, pageUrls);
progress.advance();
@@ -222,6 +248,11 @@ static String getDisplayedImageUrl(@Nonnull Document doc) {
Element e = elements.first();
if (e != null) return ParseHelper.getImgSrc(e);
}
+ elements = doc.select("#i3.img");
+ if (!elements.isEmpty()) {
+ Element e = elements.first();
+ if (e != null) return ParseHelper.getImgSrc(e);
+ }
return "";
}
@@ -286,23 +317,45 @@ static MpvInfo parseMpvPage(@NonNull final String url,
return result;
}
- @Nullable
- public Optional parseBackupUrl(@NonNull String url, @NonNull Map requestHeaders, int order, int maxPages, Chapter chapter) throws Exception {
+ static Optional parseBackupUrl(
+ @NonNull String url,
+ @NonNull Site site,
+ @NonNull Map requestHeaders,
+ int order,
+ int maxPages,
+ Chapter chapter) throws Exception {
List> reqHeaders = HttpHelper.webkitRequestHeadersToOkHttpHeaders(requestHeaders, url);
- Document doc = getOnlineDocument(url, reqHeaders, Site.EHENTAI.useHentoidAgent(), Site.EHENTAI.useWebviewAgent());
+ Document doc = getOnlineDocument(url, reqHeaders, site.useHentoidAgent(), site.useWebviewAgent());
if (doc != null) {
String imageUrl = getDisplayedImageUrl(doc).toLowerCase();
// If we have the 509.gif picture, it means the bandwidth limit for e-h has been reached
if (imageUrl.contains("/509.gif"))
- throw new LimitReachedException("Bandwidth limit reached");
+ throw new LimitReachedException(site.getDescription() + " download points regenerate over time or can be bought if you're in a hurry");
if (!imageUrl.isEmpty())
return Optional.of(ParseHelper.urlToImageFile(imageUrl, order, maxPages, StatusContent.SAVED, chapter));
}
return Optional.empty();
}
- static ImmutablePair> parseImagePageEh(@NonNull String url, @NonNull List> requestHeaders) throws IOException, LimitReachedException, EmptyResultException {
- Document doc = getOnlineDocument(url, requestHeaders, Site.EHENTAI.useHentoidAgent(), Site.EHENTAI.useWebviewAgent());
+ @Nullable
+ public Optional parseBackupUrl(@NonNull String url, @NonNull Map requestHeaders, int order, int maxPages, Chapter chapter) throws Exception {
+ return parseBackupUrl(url, Site.EHENTAI, requestHeaders, order, maxPages, chapter);
+ }
+
+ static ImmutablePair> parseImagePageMpv(@NonNull String json, @NonNull List> requestHeaders, @NonNull final Site site) throws IOException, LimitReachedException, EmptyResultException {
+ MpvImageInfo mpvInfo = JsonHelper.jsonToObject(json, MpvImageInfo.class);
+ EHentaiImageResponse imageMetadata = getMpvImage(mpvInfo, requestHeaders, site.useHentoidAgent(), site.useWebviewAgent());
+
+ String imageUrl = imageMetadata.getUrl();
+ // If we have the 509.gif picture, it means the bandwidth limit for e-h has been reached
+ if (imageUrl.contains("/509.gif"))
+ throw new LimitReachedException("E(x)-hentai download points regenerate over time or can be bought on e(x)-hentai if you're in a hurry");
+
+ return new ImmutablePair<>(imageUrl, Optional.empty());
+ }
+
+ static ImmutablePair> parseImagePageClassic(@NonNull String url, @NonNull List> requestHeaders, @NonNull final Site site) throws IOException, LimitReachedException, EmptyResultException {
+ Document doc = getOnlineDocument(url, requestHeaders, site.useHentoidAgent(), site.useWebviewAgent());
if (doc != null) {
String imageUrl = getDisplayedImageUrl(doc).toLowerCase();
// If we have the 509.gif picture, it means the bandwidth limit for e-h has been reached
@@ -317,9 +370,14 @@ static ImmutablePair> parseImagePageEh(@NonNull String
throw new EmptyResultException("Page contains no picture data : " + url);
}
+ static ImmutablePair> parseImagePage(@NonNull String url, @NonNull List> requestHeaders, @NonNull final Site site) throws IOException, LimitReachedException, EmptyResultException {
+ if (url.startsWith("http")) return parseImagePageClassic(url, requestHeaders, site);
+ else return parseImagePageMpv(url, requestHeaders, site);
+ }
+
@Override
public ImmutablePair> parseImagePage(@NonNull String url, @NonNull List> requestHeaders) throws IOException, LimitReachedException, EmptyResultException {
- return parseImagePageEh(url, requestHeaders);
+ return parseImagePage(url, requestHeaders, Site.EHENTAI);
}
/**
@@ -346,14 +404,16 @@ public void onDownloadEvent(DownloadEvent event) {
case DownloadEvent.Type.EV_PAUSE:
case DownloadEvent.Type.EV_CANCEL:
case DownloadEvent.Type.EV_SKIP:
- processHalted = true;
+ progress.haltProcess();
break;
case DownloadEvent.Type.EV_COMPLETE:
case DownloadEvent.Type.EV_PREPARATION:
case DownloadEvent.Type.EV_PROGRESS:
case DownloadEvent.Type.EV_UNPAUSE:
+ case DownloadEvent.Type.EV_INTERRUPT_CONTENT:
default:
// Other events aren't handled here
+ break;
}
}
}
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java b/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java
index 2d848edc6b..fe9b0100bb 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/images/ExHentaiParser.java
@@ -28,26 +28,16 @@
import me.devsaki.hentoid.database.domains.Content;
import me.devsaki.hentoid.database.domains.ImageFile;
import me.devsaki.hentoid.enums.Site;
-import me.devsaki.hentoid.enums.StatusContent;
import me.devsaki.hentoid.events.DownloadEvent;
-import me.devsaki.hentoid.json.sources.EHentaiImageQuery;
-import me.devsaki.hentoid.json.sources.EHentaiImageResponse;
-import me.devsaki.hentoid.parsers.ParseHelper;
-import me.devsaki.hentoid.util.Helper;
-import me.devsaki.hentoid.util.JsonHelper;
import me.devsaki.hentoid.util.exception.EmptyResultException;
import me.devsaki.hentoid.util.exception.LimitReachedException;
import me.devsaki.hentoid.util.exception.PreparationInterruptedException;
import me.devsaki.hentoid.util.network.HttpHelper;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
public class ExHentaiParser implements ImageListParser {
private final ParseProgress progress = new ParseProgress();
- private boolean processHalted = false;
-
@Override
public List parseImageList(@NonNull Content onlineContent, @NonNull Content storedContent) throws Exception {
@@ -89,125 +79,32 @@ public List parseImageList(@NonNull Content content) throws Exception
if (!elements.isEmpty()) {
String mpvUrl = elements.get(0).attr("href");
try {
- result = loadMpv(content, mpvUrl, headers, useHentoidAgent, useWebviewAgent);
+ result = EHentaiParser.loadMpv(mpvUrl, headers, useHentoidAgent, useWebviewAgent, progress);
} catch (EmptyResultException e) {
- result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent);
+ result = EHentaiParser.loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent, progress);
}
} else {
- result = loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent);
+ result = EHentaiParser.loadClassic(content, galleryDoc, headers, useHentoidAgent, useWebviewAgent, progress);
}
}
progress.complete();
// If the process has been halted manually, the result is incomplete and should not be returned as is
- if (processHalted) throw new PreparationInterruptedException();
+ if (progress.isProcessHalted()) throw new PreparationInterruptedException();
} finally {
EventBus.getDefault().unregister(this);
}
return result;
}
- @SuppressWarnings("BusyWait")
- private List loadMpv(
- @NonNull Content content,
- @NonNull final String mpvUrl,
- @NonNull final List> headers,
- boolean useHentoidAgent,
- boolean useWebviewAgent) throws IOException, EmptyResultException {
- List result = new ArrayList<>();
-
- // B.1- Open the MPV and parse gallery metadata
- EHentaiParser.MpvInfo mpvInfo = EHentaiParser.parseMpvPage(mpvUrl, headers, useHentoidAgent, useWebviewAgent);
- if (null == mpvInfo)
- throw new EmptyResultException("No exploitable data has been found on the multiple page viewer");
-
- int pageCount = Math.min(mpvInfo.pagecount, mpvInfo.images.size());
- progress.start(content.getId(), -1, pageCount);
-
- // B.2- Call the API to get the pictures URL
- for (int pageNum = 1; pageNum <= pageCount && !processHalted; pageNum++) {
- EHentaiImageQuery query = new EHentaiImageQuery(mpvInfo.gid, mpvInfo.images.get(pageNum - 1).getKey(), mpvInfo.mpvkey, pageNum);
- String jsonRequest = JsonHelper.serializeToJson(query, EHentaiImageQuery.class);
- Response response = HttpHelper.postOnlineResource(mpvInfo.api_url, headers, true, useHentoidAgent, useWebviewAgent, jsonRequest, JsonHelper.JSON_MIME_TYPE);
- ResponseBody body = response.body();
- if (null == body)
- throw new EmptyResultException("API " + mpvInfo.api_url + " returned an empty body");
- String bodyStr = body.string();
- if (!bodyStr.contains("{") || !bodyStr.contains("}"))
- throw new EmptyResultException("API " + mpvInfo.api_url + " returned non-JSON data");
-
- EHentaiImageResponse imageMetadata = JsonHelper.jsonToObject(bodyStr, EHentaiImageResponse.class);
-
- if (1 == pageNum)
- result.add(ImageFile.newCover(imageMetadata.getUrl(), StatusContent.SAVED));
- result.add(ParseHelper.urlToImageFile(imageMetadata.getUrl(), pageNum, pageCount, StatusContent.SAVED));
- progress.advance();
- // Emulate JS loader
- if (0 == pageNum % 10) Helper.pause(750);
- }
-
- return result;
- }
-
- private List loadClassic(
- @NonNull Content content,
- @NonNull final Document galleryDoc,
- @NonNull final List> headers,
- boolean useHentoidAgent,
- boolean useWebviewAgent) throws IOException {
- List result = new ArrayList<>();
-
- // A.1- Detect the number of pages of the gallery
- Elements elements = galleryDoc.select("table.ptt a");
- if (elements.isEmpty()) return result;
-
- int tabId = (1 == elements.size()) ? 0 : elements.size() - 2;
- int nbGalleryPages = Integer.parseInt(elements.get(tabId).text());
-
- progress.start(content.getId(), -1, nbGalleryPages);
-
- // 2- Browse the gallery and fetch the URL for every page (since all of them have a different temporary key...)
- List pageUrls = new ArrayList<>();
-
- EHentaiParser.fetchPageUrls(galleryDoc, pageUrls);
-
- if (nbGalleryPages > 1) {
- for (int i = 1; i < nbGalleryPages && !processHalted; i++) {
- Document pageDoc = getOnlineDocument(content.getGalleryUrl() + "/?p=" + i, headers, useHentoidAgent, useWebviewAgent);
- if (pageDoc != null) EHentaiParser.fetchPageUrls(pageDoc, pageUrls);
- progress.advance();
- }
- }
-
- // 3- Add all pages for the downloader to parse
- result.add(ImageFile.newCover(content.getCoverImageUrl(), StatusContent.SAVED));
-
- int order = 1;
- for (String pageUrl : pageUrls) {
- result.add(ImageFile.fromPageUrl(order++, pageUrl, StatusContent.SAVED, pageUrls.size()));
- }
-
- return result;
- }
-
@Nullable
public Optional parseBackupUrl(@NonNull String url, @NonNull Map requestHeaders, int order, int maxPages, Chapter chapter) throws Exception {
- List> reqHeaders = HttpHelper.webkitRequestHeadersToOkHttpHeaders(requestHeaders, url);
- Document doc = getOnlineDocument(url, reqHeaders, Site.EXHENTAI.useHentoidAgent(), Site.EXHENTAI.useWebviewAgent());
- if (doc != null) {
- String imageUrl = EHentaiParser.getDisplayedImageUrl(doc).toLowerCase();
- // If we have the 509.gif picture, it means the bandwidth limit for e-h has been reached
- if (imageUrl.contains("/509.gif"))
- throw new LimitReachedException("Exhentai download points regenerate over time or can be bought on e-hentai if you're in a hurry");
- if (!imageUrl.isEmpty())
- return Optional.of(ParseHelper.urlToImageFile(imageUrl, order, maxPages, StatusContent.SAVED, chapter));
- }
- return Optional.empty();
+ return EHentaiParser.parseBackupUrl(url, Site.EXHENTAI, requestHeaders, order, maxPages, chapter);
}
@Override
public ImmutablePair> parseImagePage(@NonNull String url, @NonNull List> requestHeaders) throws IOException, LimitReachedException, EmptyResultException {
- return EHentaiParser.parseImagePageEh(url, requestHeaders);
+ return EHentaiParser.parseImagePage(url, requestHeaders, Site.EXHENTAI);
}
/**
@@ -221,8 +118,13 @@ public void onDownloadEvent(DownloadEvent event) {
case DownloadEvent.Type.EV_PAUSE:
case DownloadEvent.Type.EV_CANCEL:
case DownloadEvent.Type.EV_SKIP:
- processHalted = true;
+ progress.haltProcess();
break;
+ case DownloadEvent.Type.EV_COMPLETE:
+ case DownloadEvent.Type.EV_PREPARATION:
+ case DownloadEvent.Type.EV_PROGRESS:
+ case DownloadEvent.Type.EV_UNPAUSE:
+ case DownloadEvent.Type.EV_INTERRUPT_CONTENT:
default:
// Other events aren't handled here
}
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/images/Manhwa18Parser.java b/app/src/main/java/me/devsaki/hentoid/parsers/images/Manhwa18Parser.java
index 78b71b1391..d811227005 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/images/Manhwa18Parser.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/images/Manhwa18Parser.java
@@ -45,7 +45,7 @@ public List parseImageListImpl(@NonNull Content onlineContent, @Nulla
List chapterLinks = doc.select("div ul a[href*=chap]");
if (chapterLinks.isEmpty()) chapterLinks = doc.select("div ul a[href*=ch-]");
- chapters = ParseHelper.getChaptersFromLinks(chapterLinks, onlineContent.getId());
+ chapters = ParseHelper.getChaptersFromLinks(chapterLinks, onlineContent.getId(), "div.chapter-time", "dd/MM/yyyy");
// If the stored content has chapters already, save them for comparison
List storedChapters = null;
@@ -65,7 +65,9 @@ public List parseImageListImpl(@NonNull Content onlineContent, @Nulla
int imgOffset = ParseHelper.getMaxImageOrder(storedChapters);
// 2. Open each chapter URL and get the image data until all images are found
+ long minEpoch = Long.MAX_VALUE;
for (Chapter chp : extraChapters) {
+ if (chp.getUploadDate() > 0) minEpoch = Math.min(minEpoch, chp.getUploadDate());
if (processHalted.get()) break;
doc = getOnlineDocument(chp.getUrl(), headers, Site.MANHWA18.useHentoidAgent(), Site.MANHWA18.useWebviewAgent());
if (doc != null) {
@@ -80,6 +82,10 @@ public List parseImageListImpl(@NonNull Content onlineContent, @Nulla
}
progressPlus();
}
+ if (minEpoch > 0) {
+ onlineContent.setUploadDate(minEpoch);
+ onlineContent.setUpdatedProperties(true);
+ }
progressComplete();
// Add cover if it's a first download
diff --git a/app/src/main/java/me/devsaki/hentoid/parsers/images/ParseProgress.java b/app/src/main/java/me/devsaki/hentoid/parsers/images/ParseProgress.java
index 18d4d5368d..ed660996f9 100644
--- a/app/src/main/java/me/devsaki/hentoid/parsers/images/ParseProgress.java
+++ b/app/src/main/java/me/devsaki/hentoid/parsers/images/ParseProgress.java
@@ -1,5 +1,7 @@
package me.devsaki.hentoid.parsers.images;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import me.devsaki.hentoid.parsers.ParseHelper;
class ParseProgress {
@@ -9,6 +11,7 @@ class ParseProgress {
private int currentStep;
private int maxSteps;
private boolean hasStarted = false;
+ private final AtomicBoolean processHalted = new AtomicBoolean(false);
void start(long contentId, long storedId, int maxSteps) {
this.contentId = contentId;
@@ -23,6 +26,14 @@ boolean hasStarted() {
return hasStarted;
}
+ boolean isProcessHalted() {
+ return processHalted.get();
+ }
+
+ void haltProcess() {
+ processHalted.set(true);
+ }
+
void advance() {
ParseHelper.signalProgress(contentId, storedId, ++currentStep, maxSteps);
}
diff --git a/app/src/main/java/me/devsaki/hentoid/util/BundleX.kt b/app/src/main/java/me/devsaki/hentoid/util/BundleX.kt
index 7c323af394..4bf6b052f2 100644
--- a/app/src/main/java/me/devsaki/hentoid/util/BundleX.kt
+++ b/app/src/main/java/me/devsaki/hentoid/util/BundleX.kt
@@ -16,6 +16,14 @@ fun Bundle.boolean(default: Boolean) = object : ReadWriteProperty
putBoolean(property.name, value)
}
+fun Bundle.bundle() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ getBundle(property.name)
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Bundle?) =
+ putBundle(property.name, value)
+}
+
fun Bundle.byte(default: Byte) = object : ReadWriteProperty {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getByte(property.name, default)
@@ -64,11 +72,11 @@ fun Bundle.float(default: Float) = object : ReadWriteProperty {
putFloat(property.name, value)
}
-fun Bundle.string(default: String?) = object : ReadWriteProperty {
+fun Bundle.string(default: String) = object : ReadWriteProperty {
override fun getValue(thisRef: Any, property: KProperty<*>) =
getString(property.name, default)
- override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) =
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: String) =
putString(property.name, value)
}
@@ -118,4 +126,117 @@ fun Bundle.intArrayList() = object : ReadWriteProperty?> {
override fun setValue(thisRef: Any, property: KProperty<*>, value: ArrayList?) =
putIntegerArrayList(property.name, value)
+}
+
+fun Bundle.boolean() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getBoolean(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean?) {
+ if (value == null)
+ remove(property.name)
+ else
+ putBoolean(property.name, value)
+ }
+}
+
+fun Bundle.byte() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getByte(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Byte?) =
+ if (value == null)
+ remove(property.name)
+ else
+ putByte(property.name, value)
+}
+
+fun Bundle.char() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getChar(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Char?) =
+ if (value == null)
+ remove(property.name)
+ else
+ putChar(property.name, value)
+}
+
+fun Bundle.short() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getShort(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Short?) =
+ if (value == null)
+ remove(property.name)
+ else
+ putShort(property.name, value)
+}
+
+fun Bundle.int() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getInt(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Int?) =
+ if (value == null)
+ remove(property.name)
+ else
+ putInt(property.name, value)
+}
+
+fun Bundle.long() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getLong(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Long?) =
+ if (value == null)
+ remove(property.name)
+ else
+ putLong(property.name, value)
+}
+
+fun Bundle.float() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getFloat(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: Float?) =
+ if (value == null)
+ remove(property.name)
+ else
+ putFloat(property.name, value)
+}
+
+fun Bundle.string() = object : ReadWriteProperty {
+ override fun getValue(thisRef: Any, property: KProperty<*>) =
+ if (containsKey(property.name))
+ getString(property.name)
+ else
+ null
+
+ override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) =
+ if (value == null)
+ remove(property.name)
+ else
+ putString(property.name, value)
}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java b/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java
index 8bfd4b153f..8c4ff01da7 100644
--- a/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java
+++ b/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java
@@ -29,6 +29,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URL;
@@ -159,7 +160,7 @@ public static void viewContentGalleryPage(@NonNull final Context context, @NonNu
Intent intent = new Intent(context, Content.getWebActivityClass(content.getSite()));
BaseWebActivityBundle bundle = new BaseWebActivityBundle();
bundle.setUrl(content.getGalleryUrl());
- intent.putExtras(bundle.toBundle());
+ intent.putExtras(bundle.getBundle());
if (wrapPin) intent = UnlockActivity.wrapIntent(context, intent);
context.startActivity(intent);
}
@@ -177,8 +178,10 @@ public static void updateContentJson(@NonNull Context context, @NonNull Content
if (null == file)
throw new IllegalArgumentException("'" + content.getJsonUri() + "' does not refer to a valid file");
- try {
- JsonHelper.updateJson(context, JsonContent.fromEntity(content), JsonContent.class, file);
+ try (OutputStream output = FileHelper.getOutputStream(context, file)) {
+ if (output != null)
+ JsonHelper.updateJson(JsonContent.fromEntity(content), JsonContent.class, output);
+ else Timber.w("JSON file creation failed for %s", file.getUri());
} catch (IOException e) {
Timber.e(e, "Error while writing to %s", content.getJsonUri());
}
@@ -265,7 +268,7 @@ public static boolean openHentoidViewer(
Timber.d("Opening: %s from: %s", content.getTitle(), content.getStorageUri());
- ImageViewerActivityBundle.Builder builder = new ImageViewerActivityBundle.Builder();
+ ImageViewerActivityBundle builder = new ImageViewerActivityBundle();
builder.setContentId(content.getId());
if (searchParams != null) builder.setSearchParams(searchParams);
if (pageNumber > -1) builder.setPageNumber(pageNumber);
@@ -942,7 +945,7 @@ public static void launchBrowserFor(@NonNull final Context context, @NonNull fin
BaseWebActivityBundle bundle = new BaseWebActivityBundle();
bundle.setUrl(targetUrl);
- intent.putExtras(bundle.toBundle());
+ intent.putExtras(bundle.getBundle());
context.startActivity(intent);
}
diff --git a/app/src/main/java/me/devsaki/hentoid/util/FileHelper.java b/app/src/main/java/me/devsaki/hentoid/util/FileHelper.java
index 34adbe6975..df351a4f73 100644
--- a/app/src/main/java/me/devsaki/hentoid/util/FileHelper.java
+++ b/app/src/main/java/me/devsaki/hentoid/util/FileHelper.java
@@ -284,6 +284,7 @@ public static OutputStream getOutputStream(@NonNull final File target) throws IO
* @return New OutputStream opened on the given file
* @throws IOException In case something horrible happens during I/O
*/
+ @Nullable
public static OutputStream getOutputStream(@NonNull final Context context, @NonNull final DocumentFile target) throws IOException {
return context.getContentResolver().openOutputStream(target.getUri(), "rwt"); // Always truncate file to whatever data needs to be written
}
@@ -695,7 +696,7 @@ public static String getMimeTypeFromFileName(@NonNull String fileName) {
public static void shareFile(final @NonNull Context context, final @NonNull Uri fileUri, final @NonNull String title) {
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("text/*");
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, title);
+ if (!title.isEmpty()) sharingIntent.putExtra(Intent.EXTRA_SUBJECT, title);
if (fileUri.toString().startsWith("file")) {
Uri legitUri = FileProvider.getUriForFile(
context,
diff --git a/app/src/main/java/me/devsaki/hentoid/util/Helper.java b/app/src/main/java/me/devsaki/hentoid/util/Helper.java
index 42f74adb57..69326fd7c9 100644
--- a/app/src/main/java/me/devsaki/hentoid/util/Helper.java
+++ b/app/src/main/java/me/devsaki/hentoid/util/Helper.java
@@ -27,6 +27,14 @@
import com.google.firebase.crashlytics.FirebaseCrashlytics;
+import org.threeten.bp.Instant;
+import org.threeten.bp.ZoneId;
+import org.threeten.bp.format.DateTimeFormatter;
+import org.threeten.bp.format.DateTimeFormatterBuilder;
+import org.threeten.bp.format.DateTimeParseException;
+import org.threeten.bp.format.ResolverStyle;
+import org.threeten.bp.temporal.ChronoField;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -36,6 +44,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Random;
import java.util.Set;
@@ -94,9 +103,9 @@ public static List getListFromPrimitiveArray(long[] input) {
}
public static Set getSetFromPrimitiveArray(long[] input) {
- Set list = new HashSet<>(input.length);
- for (long n : input) list.add(n);
- return list;
+ Set set = new HashSet<>(input.length);
+ for (long n : input) set.add(n);
+ return set;
}
/**
@@ -412,6 +421,41 @@ public static int getRandomInt(int maxExclude) {
return rand.nextInt(maxExclude);
}
+ // TODO doc
+ public static long parseDatetimeToEpoch(@NonNull String date, @NonNull String pattern) {
+ final String dateClean = date.trim().replaceAll("(?<=\\d)(st|nd|rd|th)", "");
+ final DateTimeFormatter formatter = DateTimeFormatter
+ .ofPattern(pattern)
+ .withResolverStyle(ResolverStyle.LENIENT)
+ .withLocale(Locale.ENGLISH) // To parse english expressions (e.g. month name)
+ .withZone(ZoneId.systemDefault());
+
+ try {
+ return Instant.from(formatter.parse(dateClean)).toEpochMilli();
+ } catch (DateTimeParseException e) {
+ Timber.w(e);
+ }
+ return 0;
+ }
+
+ public static long parseDateToEpoch(@NonNull String date, @NonNull String pattern) {
+ final String dateClean = date.trim().replaceAll("(?<=\\d)(st|nd|rd|th)", "");
+ final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
+ .appendPattern(pattern)
+ .parseDefaulting(ChronoField.NANO_OF_DAY, 0) // To allow passing dates without time
+ .toFormatter()
+ .withResolverStyle(ResolverStyle.LENIENT)
+ .withLocale(Locale.ENGLISH) // To parse english expressions (e.g. month name)
+ .withZone(ZoneId.systemDefault());
+
+ try {
+ return Instant.from(formatter.parse(dateClean)).toEpochMilli();
+ } catch (DateTimeParseException e) {
+ Timber.w(e);
+ }
+ return 0;
+ }
+
/**
* Update the JSON file that stores bookmarks with the current bookmarks
*
diff --git a/app/src/main/java/me/devsaki/hentoid/util/JsonHelper.java b/app/src/main/java/me/devsaki/hentoid/util/JsonHelper.java
index 33f0726f07..8e409aaabf 100644
--- a/app/src/main/java/me/devsaki/hentoid/util/JsonHelper.java
+++ b/app/src/main/java/me/devsaki/hentoid/util/JsonHelper.java
@@ -10,7 +10,6 @@
import com.squareup.moshi.Types;
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -105,27 +104,6 @@ public static DocumentFile jsonToFile(@NonNull final Context context, K obje
return file;
}
- /**
- * Serialize and save the object contents to the given existing file using the JSON format
- *
- * @param context Context to be used
- * @param object Object to serialize
- * @param type Type of the output JSON structure to use
- * @param file File to write to
- * @param Type of the given object
- * @throws IOException If anything happens during file I/O
- */
- static void updateJson(@NonNull final Context context, K object, Type type, @Nonnull DocumentFile file) throws IOException {
- if (!file.exists()) return;
-
- try (OutputStream output = FileHelper.getOutputStream(context, file)) {
- if (output != null) updateJson(object, type, output);
- else Timber.w("JSON file creation failed for %s", file.getUri());
- } catch (FileNotFoundException e) {
- Timber.e(e);
- }
- }
-
/**
* Serialize and save the object contents to the given OutputStream using the JSON format
*
@@ -135,7 +113,7 @@ static void updateJson(@NonNull final Context context, K object, Type type,
* @param Type of the given object
* @throws IOException If anything happens during file I/O
*/
- private static void updateJson(K object, Type type, @Nonnull OutputStream output) throws IOException {
+ public static void updateJson(K object, Type type, @Nonnull OutputStream output) throws IOException {
byte[] bytes = serializeToJson(object, type).getBytes();
output.write(bytes);
if (output instanceof FileOutputStream) FileHelper.sync((FileOutputStream) output);
diff --git a/app/src/main/java/me/devsaki/hentoid/util/Preferences.java b/app/src/main/java/me/devsaki/hentoid/util/Preferences.java
index 64ce152878..24928e2076 100644
--- a/app/src/main/java/me/devsaki/hentoid/util/Preferences.java
+++ b/app/src/main/java/me/devsaki/hentoid/util/Preferences.java
@@ -95,11 +95,11 @@ public static void performHousekeeping() {
isDesc = true;
break;
case (Constant.ORDER_CONTENT_LAST_DL_DATE_FIRST):
- field = Constant.ORDER_FIELD_DOWNLOAD_DATE;
+ field = Constant.ORDER_FIELD_DOWNLOAD_PROCESSING_DATE;
isDesc = true;
break;
case (Constant.ORDER_CONTENT_LAST_DL_DATE_LAST):
- field = Constant.ORDER_FIELD_DOWNLOAD_DATE;
+ field = Constant.ORDER_FIELD_DOWNLOAD_PROCESSING_DATE;
break;
case (Constant.ORDER_CONTENT_RANDOM):
field = Constant.ORDER_FIELD_RANDOM;
@@ -299,6 +299,16 @@ public static boolean getEndlessScroll() {
return sharedPreferences.getBoolean(Key.ENDLESS_SCROLL, Default.ENDLESS_SCROLL);
}
+ public static boolean isTopFabEnabled() {
+ return sharedPreferences.getBoolean(Key.TOP_FAB, Default.TOP_FAB);
+ }
+
+ public static void setTopFabEnabled(boolean value) {
+ sharedPreferences.edit()
+ .putBoolean(Key.TOP_FAB, value)
+ .apply();
+ }
+
public static boolean getRecentVisibility() {
return sharedPreferences.getBoolean(Key.APP_PREVIEW, BuildConfig.DEBUG);
}
@@ -817,6 +827,7 @@ private Key() {
static final String FIRST_RUN = "pref_first_run";
public static final String DRAWER_SOURCES = "pref_drawer_sources";
public static final String ENDLESS_SCROLL = "pref_endless_scroll";
+ public static final String TOP_FAB = "pref_top_fab";
public static final String SD_STORAGE_URI = "pref_sd_storage_uri";
public static final String EXTERNAL_LIBRARY = "pref_external_library";
public static final String EXTERNAL_LIBRARY_URI = "pref_external_library_uri";
@@ -921,6 +932,7 @@ private Default() {
static final boolean SEARCH_COUNT_ATTRIBUTE_RESULTS = true;
static final boolean FIRST_RUN = true;
static final boolean ENDLESS_SCROLL = true;
+ static final boolean TOP_FAB = true;
static final int MEMORY_ALERT = 110;
static final boolean IMPORT_QUEUE_EMPTY = false;
static final boolean EXTERNAL_LIBRARY_DELETE = false;
@@ -1010,13 +1022,14 @@ private Constant() {
public static final int ORDER_FIELD_TITLE = 0;
public static final int ORDER_FIELD_ARTIST = 1;
public static final int ORDER_FIELD_NB_PAGES = 2;
- public static final int ORDER_FIELD_DOWNLOAD_DATE = 3;
+ public static final int ORDER_FIELD_DOWNLOAD_PROCESSING_DATE = 3;
public static final int ORDER_FIELD_UPLOAD_DATE = 4;
public static final int ORDER_FIELD_READ_DATE = 5;
public static final int ORDER_FIELD_READS = 6;
public static final int ORDER_FIELD_SIZE = 7;
public static final int ORDER_FIELD_CHILDREN = 8; // Groups only
public static final int ORDER_FIELD_READ_PROGRESS = 9;
+ public static final int ORDER_FIELD_DOWNLOAD_COMPLETION_DATE = 10;
public static final int ORDER_FIELD_CUSTOM = 98;
public static final int ORDER_FIELD_RANDOM = 99;
diff --git a/app/src/main/java/me/devsaki/hentoid/util/network/HttpHelper.java b/app/src/main/java/me/devsaki/hentoid/util/network/HttpHelper.java
index 326daf400c..c43c71c477 100644
--- a/app/src/main/java/me/devsaki/hentoid/util/network/HttpHelper.java
+++ b/app/src/main/java/me/devsaki/hentoid/util/network/HttpHelper.java
@@ -248,14 +248,23 @@ public static List> webkitRequestHeadersToOkHttpHeaders(@Nu
/**
* Add current cookies of the given URL to the given headers structure
+ * If the given header already has a cookie entry, it is removed and replaced with the one
+ * associated with the given URL.
*
* @param url URL to get cookies for
- * @param headers Structure to populate
+ * @param headers Structure to populate or update
*/
public static void addCurrentCookiesToHeader(@NonNull final String url, @NonNull List> headers) {
String cookieStr = getCookies(url);
- if (!cookieStr.isEmpty())
+ if (!cookieStr.isEmpty()) {
+ for (int i = 0; i < headers.size(); i++) {
+ if (headers.get(i).first.equals(HEADER_COOKIE_KEY)) {
+ headers.remove(i);
+ break;
+ }
+ }
headers.add(new Pair<>(HEADER_COOKIE_KEY, cookieStr));
+ }
}
/**
diff --git a/app/src/main/java/me/devsaki/hentoid/viewholders/ContentItem.java b/app/src/main/java/me/devsaki/hentoid/viewholders/ContentItem.java
index 347ce044fe..5d7d5fcd2b 100644
--- a/app/src/main/java/me/devsaki/hentoid/viewholders/ContentItem.java
+++ b/app/src/main/java/me/devsaki/hentoid/viewholders/ContentItem.java
@@ -314,7 +314,7 @@ public void bindView(@NotNull ContentItem item, @NotNull List> payloads) {
// Payloads are set when the content stays the same but some properties alone change
if (!payloads.isEmpty()) {
Bundle bundle = (Bundle) payloads.get(0);
- ContentItemBundle.Parser bundleParser = new ContentItemBundle.Parser(bundle);
+ ContentItemBundle bundleParser = new ContentItemBundle(bundle);
Boolean boolValue = bundleParser.isBeingDeleted();
if (boolValue != null) item.content.setIsBeingDeleted(boolValue);
@@ -324,8 +324,8 @@ public void bindView(@NotNull ContentItem item, @NotNull List> payloads) {
if (boolValue != null) item.content.setCompleted(boolValue);
Long longValue = bundleParser.getReads();
if (longValue != null) item.content.setReads(longValue);
- longValue = bundleParser.getReadPagesCount();
- if (longValue != null) item.content.setReadPagesCount(longValue.intValue());
+ Integer intValue = bundleParser.getReadPagesCount();
+ if (intValue != null) item.content.setReadPagesCount(intValue);
String stringValue = bundleParser.getCoverUri();
if (stringValue != null) item.content.getCover().setFileUri(stringValue);
stringValue = bundleParser.getTitle();
diff --git a/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java b/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java
index 5d3405b172..d350be2b74 100644
--- a/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java
+++ b/app/src/main/java/me/devsaki/hentoid/viewholders/DuplicateItem.java
@@ -220,9 +220,9 @@ public void bindView(@NotNull DuplicateItem item, @NotNull List> payloads) {
// Payloads are set when the content stays the same but some properties alone change
if (!payloads.isEmpty()) {
Bundle bundle = (Bundle) payloads.get(0);
- DuplicateItemBundle.Parser bundleParser = new DuplicateItemBundle.Parser(bundle);
+ DuplicateItemBundle bundleParser = new DuplicateItemBundle(bundle);
- Boolean boolValue = bundleParser.getKeep();
+ Boolean boolValue = bundleParser.isKeep();
if (boolValue != null) item.keep = boolValue;
boolValue = bundleParser.isBeingDeleted();
if (boolValue != null) item.isBeingDeleted = boolValue;
diff --git a/app/src/main/java/me/devsaki/hentoid/viewholders/GroupDisplayItem.java b/app/src/main/java/me/devsaki/hentoid/viewholders/GroupDisplayItem.java
index 6055bcf813..6184255880 100644
--- a/app/src/main/java/me/devsaki/hentoid/viewholders/GroupDisplayItem.java
+++ b/app/src/main/java/me/devsaki/hentoid/viewholders/GroupDisplayItem.java
@@ -175,7 +175,7 @@ public void bindView(@NotNull GroupDisplayItem item, @NotNull List> payloads)
// Payloads are set when the content stays the same but some properties alone change
if (!payloads.isEmpty()) {
Bundle bundle = (Bundle) payloads.get(0);
- GroupItemBundle.Parser bundleParser = new GroupItemBundle.Parser(bundle);
+ GroupItemBundle bundleParser = new GroupItemBundle(bundle);
String stringValue = bundleParser.getCoverUri();
if (stringValue != null) coverUri = stringValue;
diff --git a/app/src/main/java/me/devsaki/hentoid/viewholders/ImageFileItem.java b/app/src/main/java/me/devsaki/hentoid/viewholders/ImageFileItem.java
index 473aa97435..903fa7f67c 100644
--- a/app/src/main/java/me/devsaki/hentoid/viewholders/ImageFileItem.java
+++ b/app/src/main/java/me/devsaki/hentoid/viewholders/ImageFileItem.java
@@ -165,7 +165,7 @@ public void bindView(@NotNull ImageFileItem item, @NotNull List> payloads) {
// Payloads are set when the content stays the same but some properties alone change
if (!payloads.isEmpty()) {
Bundle bundle = (Bundle) payloads.get(0);
- ImageItemBundle.Parser bundleParser = new ImageItemBundle.Parser(bundle);
+ ImageItemBundle bundleParser = new ImageItemBundle(bundle);
Boolean boolValue = bundleParser.isFavourite();
if (boolValue != null) item.image.setFavourite(boolValue);
diff --git a/app/src/main/java/me/devsaki/hentoid/viewholders/TextItem.java b/app/src/main/java/me/devsaki/hentoid/viewholders/TextItem.java
index 13b2ea2b89..d2a47035dc 100644
--- a/app/src/main/java/me/devsaki/hentoid/viewholders/TextItem.java
+++ b/app/src/main/java/me/devsaki/hentoid/viewholders/TextItem.java
@@ -49,6 +49,19 @@ public TextItem(String text, T tag, boolean centered) {
this.touchHelper = null;
this.reformatCase = true;
this.isHighlighted = false;
+ this.setSelectable(false);
+ }
+
+ public TextItem(String text, T tag, boolean reformatCase, boolean isSelected) {
+ this.text = text;
+ this.tag = tag;
+ this.centered = false;
+ this.draggable = false;
+ this.touchHelper = null;
+ this.reformatCase = reformatCase;
+ this.isHighlighted = false;
+ this.setSelected(isSelected);
+ this.setSelectable(true);
}
public TextItem(String text, T tag, boolean draggable, boolean reformatCase, boolean isHighlighted, ItemTouchHelper touchHelper) {
@@ -59,6 +72,7 @@ public TextItem(String text, T tag, boolean draggable, boolean reformatCase, boo
this.touchHelper = touchHelper;
this.reformatCase = reformatCase;
this.isHighlighted = isHighlighted;
+ this.setSelectable(true);
}
@Nullable
@@ -136,6 +150,7 @@ public void bindView(@NotNull TextItem item, @NotNull List> list) {
}
if (item.isSelected()) checkedIndicator.setVisibility(View.VISIBLE);
+ else if (item.isSelectable()) checkedIndicator.setVisibility(View.INVISIBLE);
else checkedIndicator.setVisibility(View.GONE);
title.setText(item.getDisplayText());
diff --git a/app/src/main/java/me/devsaki/hentoid/viewmodels/LibraryViewModel.java b/app/src/main/java/me/devsaki/hentoid/viewmodels/LibraryViewModel.java
index 9789bf1c46..4d63ddc7c7 100644
--- a/app/src/main/java/me/devsaki/hentoid/viewmodels/LibraryViewModel.java
+++ b/app/src/main/java/me/devsaki/hentoid/viewmodels/LibraryViewModel.java
@@ -72,6 +72,7 @@
import me.devsaki.hentoid.util.exception.EmptyResultException;
import me.devsaki.hentoid.util.network.HttpHelper;
import me.devsaki.hentoid.widget.ContentSearchManager;
+import me.devsaki.hentoid.widget.GroupSearchManager;
import me.devsaki.hentoid.workers.DeleteWorker;
import me.devsaki.hentoid.workers.PurgeWorker;
import me.devsaki.hentoid.workers.data.DeleteData;
@@ -82,43 +83,51 @@ public class LibraryViewModel extends AndroidViewModel {
// Collection DAO
private final CollectionDAO dao;
- // Library search manager
- private final ContentSearchManager searchManager;
+ // Content search manager
+ private final ContentSearchManager contentSearchManager;
+ // Groups search manager
+ private final GroupSearchManager groupSearchManager;
// Cleanup for all RxJava calls
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
// Cleanup for all work observers
private final List>> workObservers = new ArrayList<>();
- // Collection data
+ // Content data
private LiveData> currentSource;
private final LiveData totalContent;
private final MediatorLiveData> libraryPaged = new MediatorLiveData<>();
+ private final MutableLiveData contentSearchBundle = new MutableLiveData<>();
// Groups data
private final MutableLiveData group = new MutableLiveData<>();
private LiveData> currentGroupsSource;
private final MediatorLiveData> groups = new MediatorLiveData<>();
+ private LiveData> currentGroupsTotalSource;
private final MediatorLiveData currentGroupTotal = new MediatorLiveData<>();
private final MutableLiveData isCustomGroupingAvailable = new MutableLiveData<>(); // True if there's at least one existing custom group; false instead
+ private final MutableLiveData groupSearchBundle = new MutableLiveData<>();
- // Updated whenever a new search is performed
- private final MediatorLiveData newSearch = new MediatorLiveData<>();
+ // Updated whenever a new COntentsearch is performed
+ private final MediatorLiveData newContentSearch = new MediatorLiveData<>();
public LibraryViewModel(@NonNull Application application, @NonNull CollectionDAO collectionDAO) {
super(application);
dao = collectionDAO;
- searchManager = new ContentSearchManager(dao);
+ contentSearchManager = new ContentSearchManager(dao);
+ groupSearchManager = new GroupSearchManager(dao);
totalContent = dao.countAllBooks();
refreshCustomGroupingAvailable();
}
public void onSaveState(Bundle outState) {
- searchManager.saveToBundle(outState);
+ contentSearchManager.saveToBundle(outState);
+ groupSearchManager.saveToBundle(outState);
}
public void onRestoreState(@Nullable Bundle savedState) {
if (savedState == null) return;
- searchManager.loadFromBundle(savedState);
+ contentSearchManager.loadFromBundle(savedState);
+ groupSearchManager.loadFromBundle(savedState);
}
@Override
@@ -149,8 +158,8 @@ public LiveData getTotalContent() {
}
@NonNull
- public LiveData getNewSearch() {
- return newSearch;
+ public LiveData getNewContentSearch() {
+ return newContentSearch;
}
@NonNull
@@ -168,10 +177,14 @@ public LiveData isCustomGroupingAvailable() {
return isCustomGroupingAvailable;
}
- public Bundle getSearchManagerBundle() {
- Bundle bundle = new Bundle();
- searchManager.saveToBundle(bundle);
- return bundle;
+ @NonNull
+ public LiveData getContentSearchManagerBundle() {
+ return contentSearchBundle;
+ }
+
+ @NonNull
+ public LiveData getGroupSearchManagerBundle() {
+ return groupSearchBundle;
}
// =========================
@@ -182,14 +195,17 @@ public Bundle getSearchManagerBundle() {
* Perform a new library search
*/
private void doSearchContent() {
- if (currentSource != null) libraryPaged.removeSource(currentSource);
-
- searchManager.setContentSortField(Preferences.getContentSortField());
- searchManager.setContentSortDesc(Preferences.isContentSortDesc());
-
- currentSource = searchManager.getLibrary();
+ // Update search properties set directly through Preferences
+ Timber.v(">> doSearchContent");
+ contentSearchManager.setContentSortField(Preferences.getContentSortField());
+ contentSearchManager.setContentSortDesc(Preferences.isContentSortDesc());
+ if (Preferences.getGroupingDisplay().equals(Grouping.FLAT))
+ contentSearchManager.setGroup(null);
+ if (currentSource != null) libraryPaged.removeSource(currentSource);
+ currentSource = contentSearchManager.getLibrary();
libraryPaged.addSource(currentSource, libraryPaged::setValue);
+ contentSearchBundle.postValue(contentSearchManager.toBundle());
}
/**
@@ -198,9 +214,9 @@ private void doSearchContent() {
* @param query Query to use for the universal search
*/
public void searchContentUniversal(@NonNull String query) {
- searchManager.clearSelectedSearchTags(); // If user searches in main toolbar, universal search takes over advanced search
- searchManager.setQuery(query);
- newSearch.setValue(true);
+ contentSearchManager.clearSelectedSearchTags(); // If user searches in main toolbar, universal search takes over advanced search
+ contentSearchManager.setQuery(query);
+ newContentSearch.setValue(true);
doSearchContent();
}
@@ -211,9 +227,9 @@ public void searchContentUniversal(@NonNull String query) {
* @param metadata Metadata to use for the search
*/
public void searchContent(@NonNull String query, @NonNull List metadata) {
- searchManager.setQuery(query);
- searchManager.setTags(metadata);
- newSearch.setValue(true);
+ contentSearchManager.setQuery(query);
+ contentSearchManager.setTags(metadata);
+ newContentSearch.setValue(true);
doSearchContent();
}
@@ -225,23 +241,42 @@ public void clearContent() {
}
}
- /**
- * Perform a new group search using the given query
- *
- * @param query Query to use for the search
- */
- public void searchGroup(Grouping grouping, @NonNull String query, int orderField, boolean orderDesc, int artistGroupVisibility, boolean groupFavouritesOnly) {
+ public void searchGroup() {
+ doSearchGroup();
+ }
+
+ private void doSearchGroup() {
+ Timber.v(">> doSearchGroup");
+ // Update search properties set directly through Preferences
+ groupSearchManager.setSortField(Preferences.getGroupSortField());
+ groupSearchManager.setSortDesc(Preferences.isGroupSortDesc());
+ groupSearchManager.setGrouping(Preferences.getGroupingDisplay());
+ groupSearchManager.setArtistGroupVisibility(Preferences.getArtistGroupVisibility());
+
if (currentGroupsSource != null) groups.removeSource(currentGroupsSource);
- currentGroupsSource = dao.selectGroupsLive(grouping.getId(), query, orderField, orderDesc, artistGroupVisibility, groupFavouritesOnly);
+ currentGroupsSource = groupSearchManager.getGroups();
groups.addSource(currentGroupsSource, groups::setValue);
+
+ if (currentGroupsTotalSource != null)
+ currentGroupTotal.removeSource(currentGroupsTotalSource);
+ currentGroupsTotalSource = groupSearchManager.getAllGroups();
+ currentGroupTotal.addSource(currentGroupsTotalSource, list -> currentGroupTotal.postValue(list.size()));
+
+ groupSearchBundle.postValue(groupSearchManager.toBundle());
+ refreshCustomGroupingAvailable();
+ }
+
+ public void refreshCustomGroupingAvailable() {
+ isCustomGroupingAvailable.postValue(dao.countGroupsFor(Grouping.CUSTOM) > 0);
}
+
/**
* Toggle the completed filter
*/
public void toggleCompletedFilter() {
- searchManager.setFilterBookCompleted(!searchManager.isFilterBookCompleted());
- newSearch.setValue(true);
+ contentSearchManager.setFilterBookCompleted(!contentSearchManager.isFilterBookCompleted());
+ newContentSearch.setValue(true);
doSearchContent();
}
@@ -249,8 +284,8 @@ public void toggleCompletedFilter() {
* Toggle the completed filter
*/
public void toggleNotCompletedFilter() {
- searchManager.setFilterBookNotCompleted(!searchManager.isFilterBookNotCompleted());
- newSearch.setValue(true);
+ contentSearchManager.setFilterBookNotCompleted(!contentSearchManager.isFilterBookNotCompleted());
+ newContentSearch.setValue(true);
doSearchContent();
}
@@ -258,25 +293,54 @@ public void toggleNotCompletedFilter() {
* Toggle the books favourite filter
*/
public void setContentFavouriteFilter(boolean value) {
- searchManager.setFilterBookFavourites(value);
- newSearch.setValue(true);
+ contentSearchManager.setFilterBookFavourites(value);
+ newContentSearch.setValue(true);
doSearchContent();
}
/**
- * Set the mode (endless or paged)
+ * Toggle the groups favourite filter
*/
- public void setPagingMethod(boolean isEndless) {
- searchManager.setLoadAll(!isEndless);
- newSearch.setValue(true);
+ public void setGroupFavouriteFilter(boolean value) {
+ groupSearchManager.setFilterFavourites(value);
+ doSearchGroup();
+ }
+
+ public void setGroupQuery(String value) {
+ groupSearchManager.setQuery(value);
+ doSearchGroup();
+ }
+
+ public void setGrouping(int groupingId) {
+ int currentGrouping = Preferences.getGroupingDisplay().getId();
+ if (groupingId != currentGrouping) {
+ Preferences.setGroupingDisplay(groupingId);
+ if (groupingId == Grouping.FLAT.getId()) doSearchContent();
+ doSearchGroup();
+ }
+ }
+
+ public void clearGroupFilters() {
+ groupSearchManager.clearFilters();
+ doSearchGroup();
+ }
+
+ public void clearContentFilters() {
+ contentSearchManager.clearFilters();
doSearchContent();
}
/**
- * Update the order of the content list
+ * Set Content paging mode (endless or paged)
*/
- public void updateContentOrder() {
- newSearch.setValue(true);
+ public void setContentPagingMethod(boolean isEndless) {
+ contentSearchManager.setLoadAll(!isEndless);
+ newContentSearch.setValue(true);
+ doSearchContent();
+ }
+
+ public void searchContent() {
+ newContentSearch.setValue(true);
doSearchContent();
}
@@ -284,35 +348,19 @@ public void setGroup(Group group, boolean forceRefresh) {
Group currentGroup = this.group.getValue();
if (!forceRefresh && Objects.equals(group, currentGroup)) return;
+ // Reset content sorting to TITLE when reaching the Ungrouped group with CUSTOM sorting (can't work)
+ if (!group.grouping.canReorderBooks() || (group.grouping.equals(Grouping.CUSTOM) && 1 == group.getSubtype()))
+ Preferences.setContentSortField(Preferences.Constant.ORDER_FIELD_TITLE);
+
this.group.postValue(group);
- searchManager.setGroup(group);
- newSearch.setValue(true);
+ contentSearchManager.setGroup(group);
+
+ newContentSearch.setValue(true);
// Don't search now as the UI will inevitably search as well upon switching to books view
// TODO only useful when browsing custom groups ?
doSearchContent();
}
- public void setGrouping(@NonNull final Grouping grouping, int orderField, boolean orderDesc, int artistGroupVisibility, boolean groupFavouritesOnly) {
- if (grouping.equals(Grouping.FLAT)) {
- setGroup(null, false);
- return;
- }
-
- if (currentGroupsSource != null) groups.removeSource(currentGroupsSource);
- currentGroupsSource = dao.selectGroupsLive(grouping.getId(), null, orderField, orderDesc, artistGroupVisibility, groupFavouritesOnly);
- groups.addSource(currentGroupsSource, this::onGroupsChanged);
- }
-
- private void onGroupsChanged(@NonNull final List newGroups) {
- groups.setValue(newGroups);
- currentGroupTotal.postValue(newGroups.size());
- refreshCustomGroupingAvailable();
- }
-
- public void refreshCustomGroupingAvailable() {
- isCustomGroupingAvailable.postValue(dao.countGroupsFor(Grouping.CUSTOM) > 0);
- }
-
// =========================
// ========= CONTENT ACTIONS
// =========================
@@ -877,9 +925,9 @@ public void moveContentsToCustomGroup(long[] contentIds, @Nullable final Group g
}
public void resetCompletedFilter() {
- if (searchManager.isFilterBookCompleted())
+ if (contentSearchManager.isFilterBookCompleted())
toggleCompletedFilter();
- else if (searchManager.isFilterBookNotCompleted())
+ else if (contentSearchManager.isFilterBookNotCompleted())
toggleNotCompletedFilter();
}
diff --git a/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java b/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java
index 90c58d3843..b8b085f2ff 100644
--- a/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java
+++ b/app/src/main/java/me/devsaki/hentoid/viewmodels/QueueViewModel.java
@@ -304,6 +304,7 @@ public void redownloadContent(
StatusContent targetImageStatus = reparseImages ? StatusContent.ERROR : null;
AtomicInteger errorCount = new AtomicInteger(0);
+ AtomicInteger okCount = new AtomicInteger(0);
compositeDisposable.add(
Observable.fromIterable(contentList)
@@ -314,6 +315,7 @@ public void redownloadContent(
Content content = res.right.get();
// Non-blocking performance bottleneck; run in a dedicated worker
if (reparseImages) purgeItem(content);
+ okCount.incrementAndGet();
dao.addContentToQueue(
content, targetImageStatus, position,
ContentQueueManager.getInstance().isQueueActive(getApplication()));
@@ -335,13 +337,13 @@ public void redownloadContent(
errorCount.incrementAndGet();
onError.accept(new EmptyResultException("Redownload from scratch -> Content unreachable"));
}
- EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.generic_progress, 0, contentList.size() - errorCount.get(), errorCount.get(), contentList.size()));
+ EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.PROGRESS, R.id.generic_progress, 0, okCount.get(), errorCount.get(), contentList.size()));
})
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> {
if (Preferences.isQueueAutostart())
ContentQueueManager.getInstance().resumeQueue(getApplication());
- EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.generic_progress, 0, contentList.size() - errorCount.get(), errorCount.get(), contentList.size()));
+ EventBus.getDefault().post(new ProcessEvent(ProcessEvent.EventType.COMPLETE, R.id.generic_progress, 0, okCount.get(), errorCount.get(), contentList.size()));
onSuccess.accept(contentList.size() - errorCount.get());
})
.subscribe(
diff --git a/app/src/main/java/me/devsaki/hentoid/widget/ContentSearchManager.java b/app/src/main/java/me/devsaki/hentoid/widget/ContentSearchManager.java
deleted file mode 100644
index 6e7863a38f..0000000000
--- a/app/src/main/java/me/devsaki/hentoid/widget/ContentSearchManager.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package me.devsaki.hentoid.widget;
-
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.lifecycle.LiveData;
-import androidx.paging.PagedList;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-import io.reactivex.Single;
-import me.devsaki.hentoid.activities.bundles.SearchActivityBundle;
-import me.devsaki.hentoid.database.CollectionDAO;
-import me.devsaki.hentoid.database.domains.Attribute;
-import me.devsaki.hentoid.database.domains.Content;
-import me.devsaki.hentoid.database.domains.Group;
-import me.devsaki.hentoid.util.Preferences;
-
-public class ContentSearchManager {
-
- // Save state constants
- private static final String KEY_SELECTED_TAGS = "selected_tags";
- private static final String KEY_GROUP = "group";
- private static final String KEY_FILTER_FAVOURITES = "filter_favs";
- private static final String KEY_FILTER_COMPLETED_YES = "filter_completed_yes";
- private static final String KEY_FILTER_COMPLETED_NO = "filter_completed_no";
- private static final String KEY_QUERY = "query";
- private static final String KEY_SORT_FIELD = "sort_field";
- private static final String KEY_SORT_DESC = "sort_desc";
-
- private final CollectionDAO collectionDAO;
-
- // Book favourite filter
- private boolean filterBookFavourites = false;
- // Book completed filter
- private boolean filterBookCompleted = false;
- private boolean filterBookNotCompleted = false;
- // Page favourite filter
- private boolean filterPageFavourites = false;
- // Full-text query
- private String query = "";
- // Current search tags
- private List tags = new ArrayList<>();
- // Current search tags
- private boolean loadAll = false;
- // Group
- private long groupId;
- // Sort field and direction
- private int contentSortField = Preferences.getContentSortField();
- private boolean contentSortDesc = Preferences.isContentSortDesc();
-
-
- public ContentSearchManager(CollectionDAO collectionDAO) {
- this.collectionDAO = collectionDAO;
- }
-
- public void setFilterBookFavourites(boolean filterBookFavourites) {
- this.filterBookFavourites = filterBookFavourites;
- }
-
- public boolean isFilterBookFavourites() {
- return filterBookFavourites;
- }
-
- public void setFilterBookCompleted(boolean filterBookCompleted) {
- this.filterBookCompleted = filterBookCompleted;
- }
-
- public void setFilterBookNotCompleted(boolean filterBookNotCompleted) {
- this.filterBookNotCompleted = filterBookNotCompleted;
- }
-
- public boolean isFilterBookCompleted() {
- return filterBookCompleted;
- }
-
- public boolean isFilterBookNotCompleted() {
- return filterBookNotCompleted;
- }
-
- public void setFilterPageFavourites(boolean filterPageFavourites) {
- this.filterPageFavourites = filterPageFavourites;
- }
-
- public void setLoadAll(boolean loadAll) {
- this.loadAll = loadAll;
- }
-
- public void setQuery(String query) {
- this.query = query;
- }
-
- public String getQuery() {
- return (query != null) ? query : "";
- }
-
- public void setTags(List tags) {
- if (tags != null) this.tags = tags;
- else this.tags.clear();
- }
-
- public void setContentSortField(int contentSortField) {
- this.contentSortField = contentSortField;
- }
-
- public void setContentSortDesc(boolean contentSortDesc) {
- this.contentSortDesc = contentSortDesc;
- }
-
- public void setGroup(Group group) {
- if (group != null)
- groupId = group.id;
- else
- groupId = -1;
- }
-
- public List getTags() {
- return tags;
- }
-
- public void clearSelectedSearchTags() {
- if (tags != null) tags.clear();
- }
-
-
- public void saveToBundle(@Nonnull Bundle outState) {
- outState.putBoolean(KEY_FILTER_FAVOURITES, filterBookFavourites);
- outState.putBoolean(KEY_FILTER_COMPLETED_YES, filterBookCompleted);
- outState.putBoolean(KEY_FILTER_COMPLETED_NO, filterBookNotCompleted);
- outState.putString(KEY_QUERY, query);
- outState.putInt(KEY_SORT_FIELD, contentSortField);
- outState.putBoolean(KEY_SORT_DESC, contentSortDesc);
- String searchUri = SearchActivityBundle.Builder.buildSearchUri(tags).toString();
- outState.putString(KEY_SELECTED_TAGS, searchUri);
- outState.putLong(KEY_GROUP, groupId);
- }
-
- public void loadFromBundle(@Nonnull Bundle state) {
- filterBookFavourites = state.getBoolean(KEY_FILTER_FAVOURITES, false);
- filterBookCompleted = state.getBoolean(KEY_FILTER_COMPLETED_YES, false);
- filterBookNotCompleted = state.getBoolean(KEY_FILTER_COMPLETED_NO, false);
- query = state.getString(KEY_QUERY, "");
- contentSortField = state.getInt(KEY_SORT_FIELD, Preferences.getContentSortField());
- contentSortDesc = state.getBoolean(KEY_SORT_DESC, Preferences.isContentSortDesc());
-
- String searchUri = state.getString(KEY_SELECTED_TAGS);
- tags = SearchActivityBundle.Parser.parseSearchUri(Uri.parse(searchUri));
- groupId = state.getLong(KEY_GROUP);
- }
-
- public LiveData> getLibrary() {
- if (!getQuery().isEmpty())
- return collectionDAO.searchBooksUniversal(getQuery(), groupId, contentSortField, contentSortDesc, filterBookFavourites, loadAll, filterBookCompleted, filterBookNotCompleted); // Universal search
- else if (!tags.isEmpty())
- return collectionDAO.searchBooks("", groupId, tags, contentSortField, contentSortDesc, filterBookFavourites, loadAll, filterBookCompleted, filterBookNotCompleted); // Advanced search
- else
- return collectionDAO.selectRecentBooks(groupId, contentSortField, contentSortDesc, filterBookFavourites, loadAll, filterBookCompleted, filterBookNotCompleted); // Default search (display recent)
- }
-
- public Single> searchLibraryForId() {
- if (!getQuery().isEmpty())
- return collectionDAO.searchBookIdsUniversal(getQuery(), groupId, contentSortField, contentSortDesc, filterBookFavourites, filterPageFavourites, filterBookCompleted, filterBookNotCompleted); // Universal search
- else if (!tags.isEmpty())
- return collectionDAO.searchBookIds("", groupId, tags, contentSortField, contentSortDesc, filterBookFavourites, filterPageFavourites, filterBookCompleted, filterBookNotCompleted); // Advanced search
- else
- return collectionDAO.selectRecentBookIds(groupId, contentSortField, contentSortDesc, filterBookFavourites, filterPageFavourites, filterBookCompleted, filterBookNotCompleted); // Default search (display recent)
- }
-}
diff --git a/app/src/main/java/me/devsaki/hentoid/widget/ContentSearchManager.kt b/app/src/main/java/me/devsaki/hentoid/widget/ContentSearchManager.kt
new file mode 100644
index 0000000000..e28f73331e
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/widget/ContentSearchManager.kt
@@ -0,0 +1,208 @@
+package me.devsaki.hentoid.widget
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.lifecycle.LiveData
+import androidx.paging.PagedList
+import io.reactivex.Single
+import me.devsaki.hentoid.activities.bundles.SearchActivityBundle
+import me.devsaki.hentoid.activities.bundles.SearchActivityBundle.Companion.parseSearchUri
+import me.devsaki.hentoid.database.CollectionDAO
+import me.devsaki.hentoid.database.domains.Attribute
+import me.devsaki.hentoid.database.domains.Content
+import me.devsaki.hentoid.database.domains.Group
+import me.devsaki.hentoid.util.*
+import java.util.*
+
+class ContentSearchManager(val dao: CollectionDAO) {
+
+ private val values = ContentSearchBundle()
+
+
+ fun toBundle(): Bundle {
+ val result = Bundle()
+ saveToBundle(result)
+ return result
+ }
+
+ fun saveToBundle(b: Bundle) {
+ b.putAll(values.bundle)
+ }
+
+ fun loadFromBundle(b: Bundle) {
+ values.bundle.putAll(b)
+ }
+
+
+ fun setFilterBookFavourites(value: Boolean) {
+ values.filterBookFavourites = value
+ }
+
+ fun setFilterBookCompleted(value: Boolean) {
+ values.filterBookCompleted = value
+ }
+
+ fun isFilterBookCompleted(): Boolean {
+ return values.filterBookCompleted
+ }
+
+ fun setFilterBookNotCompleted(value: Boolean) {
+ values.filterBookNotCompleted = value
+ }
+
+ fun isFilterBookNotCompleted(): Boolean {
+ return values.filterBookNotCompleted
+ }
+
+ fun setFilterPageFavourites(value: Boolean) {
+ values.filterPageFavourites = value
+ }
+
+ fun setLoadAll(value: Boolean) {
+ values.loadAll = value
+ }
+
+ fun setQuery(value: String) {
+ values.query = value
+ }
+
+ fun setContentSortField(value: Int) {
+ values.sortField = value
+ }
+
+ fun setContentSortDesc(value: Boolean) {
+ values.sortDesc = value
+ }
+
+ fun setGroup(value: Group?) {
+ if (value != null) values.groupId = value.id else values.groupId = -1
+ }
+
+ fun setTags(tags: List?) {
+ if (tags != null) {
+ values.searchUri = SearchActivityBundle.buildSearchUri(tags).toString();
+ } else clearSelectedSearchTags();
+ }
+
+ fun clearSelectedSearchTags() {
+ values.searchUri = SearchActivityBundle.buildSearchUri(Collections.emptyList()).toString()
+ }
+
+ fun clearFilters() {
+ clearSelectedSearchTags()
+ setQuery("")
+ setFilterBookFavourites(false)
+ setFilterBookCompleted(false)
+ setFilterBookNotCompleted(false)
+ setFilterPageFavourites(false)
+ }
+
+ fun getLibrary(): LiveData> {
+ val tags = parseSearchUri(Uri.parse(values.searchUri))
+ return when {
+ values.query.isNotEmpty() -> dao.searchBooksUniversal(
+ values.query,
+ values.groupId,
+ values.sortField,
+ values.sortDesc,
+ values.filterBookFavourites,
+ values.loadAll,
+ values.filterBookCompleted,
+ values.filterBookNotCompleted
+ ) // Universal search
+ tags.isNotEmpty() -> dao.searchBooks(
+ "",
+ values.groupId,
+ tags,
+ values.sortField,
+ values.sortDesc,
+ values.filterBookFavourites,
+ values.loadAll,
+ values.filterBookCompleted,
+ values.filterBookNotCompleted
+ ) // Advanced search
+ else -> dao.selectRecentBooks(
+ values.groupId,
+ values.sortField,
+ values.sortDesc,
+ values.filterBookFavourites,
+ values.loadAll,
+ values.filterBookCompleted,
+ values.filterBookNotCompleted
+ )
+ } // Default search (display recent)
+ }
+
+ fun searchLibraryForId(): Single> {
+ val tags = parseSearchUri(Uri.parse(values.searchUri))
+ return when {
+ values.query.isNotEmpty() -> dao.searchBookIdsUniversal(
+ values.query,
+ values.groupId,
+ values.sortField,
+ values.sortDesc,
+ values.filterBookFavourites,
+ values.filterPageFavourites,
+ values.filterBookCompleted,
+ values.filterBookNotCompleted
+ ) // Universal search
+ tags.isNotEmpty() -> dao.searchBookIds(
+ "",
+ values.groupId,
+ tags,
+ values.sortField,
+ values.sortDesc,
+ values.filterBookFavourites,
+ values.filterPageFavourites,
+ values.filterBookCompleted,
+ values.filterBookNotCompleted
+ ) // Advanced search
+ else -> dao.selectRecentBookIds(
+ values.groupId,
+ values.sortField,
+ values.sortDesc,
+ values.filterBookFavourites,
+ values.filterPageFavourites,
+ values.filterBookCompleted,
+ values.filterBookNotCompleted
+ )
+ } // Default search (display recent)
+ }
+
+
+ // INNER CLASS
+
+ class ContentSearchBundle(val bundle: Bundle = Bundle()) {
+
+ var loadAll by bundle.boolean(default = false)
+
+ var filterPageFavourites by bundle.boolean(default = false)
+
+ var filterBookFavourites by bundle.boolean(default = false)
+
+ var filterBookCompleted by bundle.boolean(default = false)
+
+ var filterBookNotCompleted by bundle.boolean(default = false)
+
+ var query by bundle.string(default = "")
+
+ var sortField by bundle.int(default = Preferences.getContentSortField())
+
+ var sortDesc by bundle.boolean(default = Preferences.isContentSortDesc())
+
+ var searchUri by bundle.string(default = "")
+
+ var groupId by bundle.long(default = -1)
+
+
+ fun isFilterActive(): Boolean {
+ val tags = SearchActivityBundle.parseSearchUri(Uri.parse(searchUri))
+ return query.isNotEmpty()
+ || tags.isNotEmpty()
+ || filterBookFavourites
+ || filterBookCompleted
+ || filterBookNotCompleted
+ || filterPageFavourites
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/widget/GroupSearchManager.kt b/app/src/main/java/me/devsaki/hentoid/widget/GroupSearchManager.kt
new file mode 100644
index 0000000000..db1884b8d3
--- /dev/null
+++ b/app/src/main/java/me/devsaki/hentoid/widget/GroupSearchManager.kt
@@ -0,0 +1,104 @@
+package me.devsaki.hentoid.widget
+
+import android.os.Bundle
+import androidx.lifecycle.LiveData
+import me.devsaki.hentoid.database.CollectionDAO
+import me.devsaki.hentoid.database.domains.Group
+import me.devsaki.hentoid.enums.Grouping
+import me.devsaki.hentoid.util.Preferences
+import me.devsaki.hentoid.util.boolean
+import me.devsaki.hentoid.util.int
+import me.devsaki.hentoid.util.string
+
+class GroupSearchManager(val dao: CollectionDAO) {
+
+ private val values = GroupSearchBundle()
+
+
+ fun toBundle(): Bundle {
+ val result = Bundle()
+ saveToBundle(result)
+ return result
+ }
+
+ fun saveToBundle(b: Bundle) {
+ b.putAll(values.bundle)
+ }
+
+ fun loadFromBundle(b: Bundle) {
+ values.bundle.putAll(b)
+ }
+
+ fun setFilterFavourites(value: Boolean) {
+ values.filterFavourites = value
+ }
+
+ fun setQuery(value: String) {
+ values.query = value
+ }
+
+ fun setGrouping(value: Grouping) {
+ values.groupingId = value.id
+ }
+
+ fun setArtistGroupVisibility(value: Int) {
+ values.artistGroupVisibility = value
+ }
+
+ fun setSortField(value: Int) {
+ values.sortField = value
+ }
+
+ fun setSortDesc(value: Boolean) {
+ values.sortDesc = value
+ }
+
+ fun clearFilters() {
+ setQuery("")
+ setArtistGroupVisibility(Preferences.Constant.ARTIST_GROUP_VISIBILITY_ARTISTS_GROUPS)
+ setFilterFavourites(false)
+ }
+
+ fun getGroups(): LiveData> {
+ return dao.selectGroupsLive(
+ values.groupingId,
+ values.query,
+ values.sortField,
+ values.sortDesc,
+ values.artistGroupVisibility,
+ values.filterFavourites
+ )
+ }
+
+ fun getAllGroups(): LiveData> {
+ return dao.selectGroupsLive(
+ values.groupingId,
+ "",
+ values.sortField,
+ values.sortDesc,
+ Preferences.Constant.ARTIST_GROUP_VISIBILITY_ARTISTS_GROUPS,
+ false
+ )
+ }
+
+ class GroupSearchBundle(val bundle: Bundle = Bundle()) {
+
+ var filterFavourites by bundle.boolean(default = false)
+
+ var artistGroupVisibility by bundle.int(default = Preferences.getArtistGroupVisibility())
+
+ var query by bundle.string(default = "")
+
+ var groupingId by bundle.int(default = Preferences.getGroupingDisplay().id)
+
+ var sortField by bundle.int(default = Preferences.getGroupSortField())
+
+ var sortDesc by bundle.boolean(default = Preferences.isGroupSortDesc())
+
+ fun isFilterActive(): Boolean {
+ return query.isNotEmpty()
+ || artistGroupVisibility != Preferences.Constant.ARTIST_GROUP_VISIBILITY_ARTISTS_GROUPS
+ || filterFavourites
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/devsaki/hentoid/widget/ScrollPositionListener.java b/app/src/main/java/me/devsaki/hentoid/widget/ScrollPositionListener.java
index 5b8818a0ae..58e8f62698 100644
--- a/app/src/main/java/me/devsaki/hentoid/widget/ScrollPositionListener.java
+++ b/app/src/main/java/me/devsaki/hentoid/widget/ScrollPositionListener.java
@@ -74,16 +74,20 @@ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newStat
if (!(llm.findLastVisibleItemPosition() == llm.getItemCount() - 1 || 0 == llm.findFirstVisibleItemPosition()))
return;
+ if (null == onEndOutOfBoundScroll || null == onStartOutOfBoundScroll) return;
+
int scrollDirection = 0;
if (llm instanceof PrefetchLinearLayoutManager)
scrollDirection = ((PrefetchLinearLayoutManager) llm).getRawDeltaPx();
if (recyclerView.computeHorizontalScrollOffset() == dragStartPositionX && !isSettlingX && llm.canScrollHorizontally()) {
- if ((!llm.getReverseLayout() && scrollDirection >= 0) || (llm.getReverseLayout() && scrollDirection < 0)) onEndOutOfBoundScroll.run();
+ if ((!llm.getReverseLayout() && scrollDirection >= 0) || (llm.getReverseLayout() && scrollDirection < 0))
+ onEndOutOfBoundScroll.run();
else onStartOutOfBoundScroll.run();
}
if (recyclerView.computeVerticalScrollOffset() == dragStartPositionY && !isSettlingY && llm.canScrollVertically()) {
- if ((!llm.getReverseLayout() && scrollDirection >= 0) || (llm.getReverseLayout() && scrollDirection < 0)) onEndOutOfBoundScroll.run();
+ if ((!llm.getReverseLayout() && scrollDirection >= 0) || (llm.getReverseLayout() && scrollDirection < 0))
+ onEndOutOfBoundScroll.run();
else onStartOutOfBoundScroll.run();
}
}
diff --git a/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java b/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java
index 89d9aad405..1e1af46b05 100644
--- a/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java
+++ b/app/src/main/java/me/devsaki/hentoid/workers/ContentDownloadWorker.java
@@ -725,12 +725,17 @@ private void completeDownload(final long contentId, @NonNull final String title,
// Compute perceptual hash for the cover picture
ContentHelper.computeAndSaveCoverHash(getApplicationContext(), content, dao);
- // Mark content as downloaded
+ // Mark content as downloaded (download processing date; if none set before)
if (0 == content.getDownloadDate())
content.setDownloadDate(Instant.now().toEpochMilli());
- content.setStatus((0 == pagesKO && !hasError) ? StatusContent.DOWNLOADED : StatusContent.ERROR);
- // Clear download params from content
- if (0 == pagesKO && !hasError) content.setDownloadParams("");
+
+ if (0 == pagesKO && !hasError) {
+ content.setDownloadParams("");
+ content.setDownloadCompletionDate(Instant.now().toEpochMilli());
+ content.setStatus(StatusContent.DOWNLOADED);
+ } else {
+ content.setStatus(StatusContent.ERROR);
+ }
content.computeSize();
// Save JSON file
diff --git a/app/src/main/res/color/secondary_color_selector_check.xml b/app/src/main/res/color/secondary_color_selector_check.xml
new file mode 100644
index 0000000000..3243206a5a
--- /dev/null
+++ b/app/src/main/res/color/secondary_color_selector_check.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/secondary_text_color_selector_check.xml b/app/src/main/res/color/secondary_text_color_selector_check.xml
new file mode 100644
index 0000000000..2cf6a5dcf3
--- /dev/null
+++ b/app/src/main/res/color/secondary_text_color_selector_check.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_action_sort.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_sort.xml
rename to app/src/main/res/drawable/ic_action_sort.xml
diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_checked.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_check.xml
rename to app/src/main/res/drawable/ic_checked.xml
diff --git a/app/src/main/res/drawable/ic_checked_circle.xml b/app/src/main/res/drawable/ic_checked_circle.xml
index e545abcc69..0c61bb1852 100644
--- a/app/src/main/res/drawable/ic_checked_circle.xml
+++ b/app/src/main/res/drawable/ic_checked_circle.xml
@@ -1,5 +1,10 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_check_square.xml b/app/src/main/res/drawable/ic_checked_square.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_check_square.xml
rename to app/src/main/res/drawable/ic_checked_square.xml
diff --git a/app/src/main/res/drawable/ic_completed_single.xml b/app/src/main/res/drawable/ic_completed_empty.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_completed_single.xml
rename to app/src/main/res/drawable/ic_completed_empty.xml
diff --git a/app/src/main/res/drawable/ic_completed_filter_off.xml b/app/src/main/res/drawable/ic_completed_filter_off.xml
deleted file mode 100644
index 64237f0c40..0000000000
--- a/app/src/main/res/drawable/ic_completed_filter_off.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_completed_filter_on.xml b/app/src/main/res/drawable/ic_completed_filter_on.xml
deleted file mode 100644
index 13ae5345c2..0000000000
--- a/app/src/main/res/drawable/ic_completed_filter_on.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_uncheck.xml b/app/src/main/res/drawable/ic_unchecked.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_uncheck.xml
rename to app/src/main/res/drawable/ic_unchecked.xml
diff --git a/app/src/main/res/drawable/ic_action_gallery.xml b/app/src/main/res/drawable/ic_view_gallery.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_action_gallery.xml
rename to app/src/main/res/drawable/ic_view_gallery.xml
diff --git a/app/src/main/res/drawable/ic_view_list.xml b/app/src/main/res/drawable/ic_view_list.xml
new file mode 100644
index 0000000000..e354868f09
--- /dev/null
+++ b/app/src/main/res/drawable/ic_view_list.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml
index 8b5bf8bb67..d46c8c628b 100644
--- a/app/src/main/res/layout/fragment_library.xml
+++ b/app/src/main/res/layout/fragment_library.xml
@@ -28,7 +28,7 @@
app:menu="@menu/library_selection_menu"
app:navigationIcon="@drawable/ic_arrow_back" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_library_search_sort_bar.xml b/app/src/main/res/layout/include_library_search_sort_bar.xml
deleted file mode 100644
index a76f790141..0000000000
--- a/app/src/main/res/layout/include_library_search_sort_bar.xml
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_library_search_subbar.xml b/app/src/main/res/layout/include_library_search_subbar.xml
new file mode 100644
index 0000000000..cc5bcfcf93
--- /dev/null
+++ b/app/src/main/res/layout/include_library_search_subbar.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_library_sort_filter_bottom_panel.xml b/app/src/main/res/layout/include_library_sort_filter_bottom_panel.xml
new file mode 100644
index 0000000000..565f62a020
--- /dev/null
+++ b/app/src/main/res/layout/include_library_sort_filter_bottom_panel.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_search_filter_category.xml b/app/src/main/res/layout/include_search_bottom_panel.xml
similarity index 93%
rename from app/src/main/res/layout/include_search_filter_category.xml
rename to app/src/main/res/layout/include_search_bottom_panel.xml
index 24e84fa3ca..b809a6e3b8 100644
--- a/app/src/main/res/layout/include_search_filter_category.xml
+++ b/app/src/main/res/layout/include_search_bottom_panel.xml
@@ -12,17 +12,17 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
- android:text="@string/app_intro"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/tag_wait_image"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="Category search" />
@@ -32,10 +32,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
- android:text="@string/app_intro"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/tag_suggestion"
- app:layout_constraintTop_toBottomOf="@+id/tag_wait_image" />
+ app:layout_constraintTop_toBottomOf="@+id/tag_wait_image"
+ tools:text="X results" />
diff --git a/app/src/main/res/menu/library_books_sort_popup.xml b/app/src/main/res/menu/library_books_sort_popup.xml
deleted file mode 100644
index a73663dc99..0000000000
--- a/app/src/main/res/menu/library_books_sort_popup.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
diff --git a/app/src/main/res/menu/library_groups_popup.xml b/app/src/main/res/menu/library_groups_popup.xml
deleted file mode 100644
index 5909f95f15..0000000000
--- a/app/src/main/res/menu/library_groups_popup.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
diff --git a/app/src/main/res/menu/library_groups_sort_popup.xml b/app/src/main/res/menu/library_groups_sort_popup.xml
deleted file mode 100644
index e78d04aee7..0000000000
--- a/app/src/main/res/menu/library_groups_sort_popup.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
diff --git a/app/src/main/res/menu/library_groups_visibility_popup.xml b/app/src/main/res/menu/library_groups_visibility_popup.xml
deleted file mode 100644
index 03ef2bd418..0000000000
--- a/app/src/main/res/menu/library_groups_visibility_popup.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
diff --git a/app/src/main/res/menu/library_menu.xml b/app/src/main/res/menu/library_menu.xml
index f80a415b4d..dfe2916caa 100644
--- a/app/src/main/res/menu/library_menu.xml
+++ b/app/src/main/res/menu/library_menu.xml
@@ -12,6 +12,13 @@
android:tooltipText="@string/menu_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="collapseActionView|always" />
+
-
-
+ app:showAsAction="ifRoom" />
+
+
+
diff --git a/app/src/main/res/menu/library_selection_menu.xml b/app/src/main/res/menu/library_selection_menu.xml
index b977b80a99..57537e53e1 100644
--- a/app/src/main/res/menu/library_selection_menu.xml
+++ b/app/src/main/res/menu/library_selection_menu.xml
@@ -19,7 +19,7 @@
app:showAsAction="always" />
diff --git a/app/src/main/res/raw/sites.json b/app/src/main/res/raw/sites.json
index 43d05655b6..51c8085796 100644
--- a/app/src/main/res/raw/sites.json
+++ b/app/src/main/res/raw/sites.json
@@ -7,7 +7,8 @@
"requestsCapPerSecond": 2
},
"NHENTAI": {
- "useHentoidAgent": true
+ "useHentoidAgent": false,
+ "useWebviewAgent": false
},
"TSUMINO": {
"useHentoidAgent": true
diff --git a/app/src/main/res/values-es/strings_slides.xml b/app/src/main/res/values-es/strings_slides.xml
index df09794f80..659a74a316 100644
--- a/app/src/main/res/values-es/strings_slides.xml
+++ b/app/src/main/res/values-es/strings_slides.xml
@@ -1,19 +1,19 @@
-¡Bienvenido a Hentoid!
+ ¡Bienvenido a Hentoid!
Hentoid es una aplicación para archivar Doujinshi y manga Hentai.
-Permisos
+ Permisos
Necesitamos de tu permiso para que se pueda almacenar archivos en tu dispositivo.
-Librería
+ Librería
Hentoid requiere una carpeta dedicada para almacenar tus archivos.
Omitir
¿Omitir?
No se podrá hacer una descarga si omites este paso. Para hacer una descarga tendrás que configurar una carpeta desde la configuración de la aplicación.
-Tema
+ Tema
Escoge un tema.
-Fuentes
+ Fuentes
Selecciona una fuente de donde quieres descargar.
Puedes cambiar esta selección en cualquier momento al utilizar el botón Edición en el menú izquierdo.
-¡Todo esta configurado!
+ ¡Todo esta configurado!
¡Estamos listos! Disfruta de la aplicación.
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 30cff11ab3..47517e7042 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -20,7 +20,6 @@
- fichiers
- fichiers
- Annuler
Arrêter
Reprendre
Annuler
@@ -60,7 +59,6 @@
Le nouvel ordre personnalisé que vous êtes sur le point de créer écrasera le précédent.
Ordonner les livres
Supprimer
- Supprimer des éléments
Déplacer dans le groupe…
Ouvrir le dossier contenant
Retélécharger
@@ -93,10 +91,7 @@
Inverser la file d\'attente
Tout retélécharger
Annuler toutes les erreurs
- Annulé
Déplacer vers le haut
- Monter
- Descendre
Déplacer vers le bas
File d\'attente en pause
diff --git a/app/src/main/res/values-fr/strings_slides.xml b/app/src/main/res/values-fr/strings_slides.xml
index 262fafa597..c27496736a 100644
--- a/app/src/main/res/values-fr/strings_slides.xml
+++ b/app/src/main/res/values-fr/strings_slides.xml
@@ -1,19 +1,19 @@
-Bienvenue sur Hentoid !
+ Bienvenue sur Hentoid !
Hentoid est une appli d\'archivage \nde doujinshi et de manga h.
-Autorisations
+ Autorisations
Nous devons vous demander la permission de stocker des fichiers sur votre appareil.
-Bibliothèque
+ Bibliothèque
Hentoid nécessite un dossier dédié pour le stockage de vos médias.
Ignorer
Ignorer \?
Vous ne pourrez pas télécharger si vous sautez cette étape. \nPour pouvoir télécharger, vous devrez définir le dossier de téléchargement dans les paramètres de l\'application.
-Thème
+ Thème
Choisissez un thème d\'affichage.
-Sources
+ Sources
Sélectionnez les sources de téléchargement.
Vous pouvez modifier cette sélection ultérieurement, à tout moment, en utilisant le bouton d\'édition du menu de gauche.
-Tout est prêt !
+ Tout est prêt !
Nous sommes prêts ! \nProfitez bien de l\'appli.
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index b70e87aa8d..0759c89367 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -8,9 +8,9 @@
Ki
Összes kiválasztása
Egyik sem
- Tovább az oldalra...
+ Tovább az oldalra…
Elfogadás
- Kérlek várj...
+ Kérlek várj…
- könyv
- könyvek
@@ -23,7 +23,6 @@
- fájl
- fájlok
- Visszavonás
Megállítás
Leállítás
Folytatás
@@ -49,9 +48,9 @@
Hentoid mappa kiválasztása
Külső mappa kiválasztása
Webhely mappáinak beolvasása
- Könyvtár beolvasása...
+ Könyvtár beolvasása…
Könyvtár beolvasása (%1$d / %2$d)
- A várólista importálása...
+ A várólista importálása…
Kérjük, válasszon egy már létező Hentoid mappát. A mappa helye megjelenik a képernyőn.
Kérjük, engedélyezze az írási jogosultságot
Keresés
@@ -65,8 +64,7 @@
Az új rendezés felülfogja írni az előzőt.
Könyvek rendezése
Törlés
- Elemek törlése
- Csoporthoz hozzárendelés...
+ Csoporthoz hozzárendelés…
Tartalmazó mappa megnyitása
Újratöltés
Újratöltés a nulláról
@@ -97,10 +95,7 @@
Várakozási sor megfordítása
Összes újbóli letöltése
Összes hibás visszavonása
- Visszavonva
Mozgatás a legtetejére
- Mozgatás fölfelé
- Mozgatás lefelé
Mozgatás az aljára
Várakozási sor megállítva
Letöltés előkészítése
@@ -184,10 +179,9 @@
Hentoid könyvtár
Külső könyvtár
Az összes mappa átnevezése az aktuális elnevezési és maximális hosszúsági beállításoknak megfelelően
- Távolítsa el a mappákat, ahol...
+ Távolítsa el a mappákat, ahol…
JSON fájl hiányzik (ajánlott)
Nincs letöltött kép
- A JSON fájl nem olvasható (nem ajánlott)
vagy
Törölje az összeset a kedvencek kivételével
%1$d / %2$d %3$s
@@ -251,7 +245,6 @@
EXPORTÁLÁS ELINDÍTÁSA
Beállítások importálása
Importálva
-Alkalmazás beállításainak megnyitása
A folytatáshoz szükségünk lenne néhány engedély elfogadásához
A folytatás előtt manuálisan meg kell adnia az engedélyeket.
Az importálás megszakítva
@@ -267,10 +260,7 @@
Importált könyvtárak
Importálás elkezdése
Könyvtár importálása
- Importálás, kérjük várjon...
- Frissítés, kérjük várjon...
Importálás befejezve
- Importálás befejezve
- %s sikeresen importálva
- %s sikeresen importálva
@@ -302,7 +292,6 @@
Érvénytelen mappa
Hiba a fájl megnyitásakor: nem találtunk ehhez a fájlhoz releváns alkalmazást
Hiba a link megnyitásakor: nem találtunk böngészőt az eszközén
- Hiba az archívum elküldésekor: nem találtunk az archívum elküldéséhez releváns alkalmazást
Megpróbáljuk újra letölteni\?
Letöltött képek száma: %1$d\nHibamentesen letöltött képek: %2$d\nHibásan letöltött képek: %3$d
Első hiba: %s
@@ -361,7 +350,6 @@
Betöltés~
Nincsen eredmény :(
Ez nincs itt, menj és töltsd le.
- Fájlok becsomagolása. \nKérjük, várjon...
Keresés \'%s\' ban/ben
Indítási kód: %s
@@ -372,7 +360,6 @@
- %1$d/%2$d eredmény
- %1$d/%2$d eredmények
- Az engedélyek visszaállítása megtörtént.\nAz alkalmazás visszaállítása.
\"A változtatások alkalmazásra kerülnek a Hentoid újraindításakor\"
- A kiválasztott könyv újbóli letöltése a nulláról\?
@@ -398,7 +385,6 @@
- Egy kiválasztott könyv már beállítva streamelésre vagy már a külső könyvtárban van\nEz a könyv figyelmen kívül lesz hagyva
- %d kiválasztott könyv már beállítva streamelésre vagy már a külső könyvtárban vannak\nEzek a kiválasztott könyvek figyelmen kívül lesznek hagyva,
- A könyvet hamarosan töröljük...
Keressen bármire: cím, szerző, címke\\és indítókód is!
Gyorsabban böngészhet az oldalszámok közötti csúsztatással
@@ -414,7 +400,7 @@
Beállítás mint, a csoport borítójaként
Ennek a könyvnek a borítóképe beállítása a jelenlegi csoport borítóképére\?
- Egyéni csoporthoz hozzárendelés...
+ Egyéni csoporthoz hozzárendelés…
Létező csoport
Új létrehozása
Új egyedi csoport név
@@ -437,8 +423,8 @@
Nincs mit törölni - ellenőrizze a \"Külső tartalom törlésének engedélyezése\" lehetőséget, hogy ez törölhető lehessen
Egyesítés
- Könyvek egyesítése...
- Könyvek szétválasztása...
+ Könyvek egyesítése…
+ Könyvek szétválasztása…
Szétválasztás
Új cím
Ezen könyvek törlése egyesítés után
@@ -483,10 +469,6 @@ Minden kijelölt fejezet egy új könyvet hoz létre
Frissítés letöltése
Készen állunk a frissítés telepítésére!
Koppintson a telepítéshez!
- Hiba a frissítési függőség elemzése során.\nKérlek, próbáld meg később újra.
- Hálózati hiba történt, kérjük próbálja meg később.
- Nem található meg a frissítési fájl a szerveren
- Frissítés ellenőrző: Nincsen új frissítés.
Kérjük figyeljen arra, hogy a letöltések ezen az oldalon meghiúsulhatnak
A letöltési gomb jelenleg nem használható ezen az oldalon
Ez az oldal jelenleg komoly stabilitási problémákkal küzd. Az alkalmazás nem tud mit kezdeni vele. Kérjük, próbálja meg később újra.
@@ -494,12 +476,10 @@ Minden kijelölt fejezet egy új könyvet hoz létre
A Hentoid fejlesztőcsapat dolgozik a javításon. Az alkalmazás értesítést fog küldeni, ha az új verzió elkészül.
A javítás elérhető az alkalmazás legújabb verziójában. Kérjük, frissítse az alkalmazást.
Kérjük írja be a PIN-kódot
- Alkalmazászár PIN-kód
Alkalmazászár bekapcsolva
Alkalmazászár kikapcsolva
PIN-kód alaphelyzetbe állítása
PIN-kód alaphelyzetbe állítása
- PIN RENDBEN!
Kérjük írja be a jelenlegi PIN-kódját
Kérjük írja be az új PIN-kódot
Kérjük írja be újra az új PIN-kódot
@@ -535,8 +515,6 @@ háttérből
Kezdőlap
Torrent letöltése sikertelen: %s
Tárolási engedély megtagadva - a letöltő nem használható
-Kérjük, hogy használjon vagy hozzon létre egy legális FAKKU fiókot.\nHentoid nem fog működni anélkül.
- Ezt a könyvet a jelenlegi FAKKU hozzáféréseddel nem tudod megtekinteni.
Kérjük, használjon vagy hozzon létre egy exHentai fiókot.
Érvénytelen belépési adatok, kérjük jelentkezzen be újra
Nem teljesek a belépési adatok, kérjük, jelentkezzen be újra. Ha a probléma továbbra is fennáll, fontolja meg, hogy más országból lépjen be a webhelyre, vagy hozzon létre új fiókot.
@@ -566,11 +544,9 @@ háttérből
- %s találat
Szűrő kiválasztása
-Karbantartás elvégzése
Nem sikerült betölteni a könyvet: Nem található kép
Betöltés: %1$d/%2$d
Kép betöltése: %d%%
- A kép nem található
%1$d x %2$d (skála %3$.0f%%) - %4$s
Válassza ki az olvasás irányát
Balról, jobbra
@@ -585,12 +561,9 @@ háttérből
Összes oldal mutatása
A kedvenc oldal megjelenítésének beállítása
Borítóképként beállítás
- Oldalmenü
- Oldal tulajdonságainak megnyitása
Könyv törlése
Átmásolva a Letöltések mappába
Nem lehet írni a Letöltések mappába
- Töröljük ezt a könyvet\?
Töröljük ezt az oldalt\?
Információ
Előző könyv
@@ -607,14 +580,10 @@ háttérből
Szeretné beállítani ezt az oldalt a könyv borítójára?
A kép nem található
- Kép streamelése...
+ Kép streamelése…
Könyv beállítások
Alkalmazás beállítások
Alkalmazás beállításainak használata (%s)
- Az oldal részletei
- A könyv részletei
- Kedvenc oldalra váltás
- Kedvenc könyvre váltás
Oldal kedvencnek jelölve
Oldal kedvencekből kijelölés
Könyv kedvencnek jelölve
@@ -675,7 +644,6 @@ háttérből
Borítókép: nincsen adat
Szerző: %.0f%%
Szerző: nincsen adat
- Elfogadás (%d választási lehetőséget)
Választások elmentése
Duplikátum felderítés elkezdődött
Duplikátum felderítés befejeződött
@@ -684,7 +652,7 @@ háttérből
- %d duplikátumok detektálva
Semmi sem jeleníthető meg - Új duplikátum keresés futtatása
- Duplikátumok felderítése...
+ Duplikátumok felderítése…
- %d duplikátum
- %d duplikátumok
@@ -695,14 +663,12 @@ háttérből
Duplikátum figyelmeztetés!
A letölteni kívánt könyv lehet, hogy egy olyan könyv másolata, amely már a tulajdonában van.
A könyv, amelyből oldalakat fog letölteni, nem feltétlenül az a könyv, amelyik már a tulajdonában van.
- Könyvtárban
- Várakozási sorban van
Mindig töltsd le, és ne kérdezd újra
Soha többé ne adjon hozzá oldalakat potenciális másodpéldányokból
Hasonlóság: %.0f%%
Könyv letöltése
Extra oldalak letöltése
- Indexelés...
+ Indexelés…
Tartalom archiválása
Tartalom törlése
A törlés sikeres
@@ -713,7 +679,7 @@ háttérből
A törlés sikertelen
Legalább egy könyvet nem sikerült törölni
Indítás
- Kezdjük...
+ Kezdjük…
Indítás befejezve
Ma
Az elmúlt 7napban
@@ -743,7 +709,6 @@ háttérből
török
cseh
Válassza ki a fájlkezelőjét
- Alapértelmezett jogosultság
bájt/bájtok
KB
MB
diff --git a/app/src/main/res/values-hu/strings_slides.xml b/app/src/main/res/values-hu/strings_slides.xml
index f34af8f65c..6be930dc54 100644
--- a/app/src/main/res/values-hu/strings_slides.xml
+++ b/app/src/main/res/values-hu/strings_slides.xml
@@ -1,19 +1,19 @@
-Üdvözlünk a Hentoid alkalmazásban!
+ Üdvözlünk a Hentoid alkalmazásban!
A Hentoid az egy Doujinshi\n& Hentai Manga archiváló alkalmazás.
-Engedélyek
+ Engedélyek
Szükségünk van néhány engedélyre, hogy adatokat tárolhassunk a eszközén.
-Könyvtár
+ Könyvtár
A Hentoid alkalmazásnak szüksége van egy dedikált mappának, a médiád eltárolásához.
Átugrás
Átugrás\?
Nem fogsz tudni letölteni, hogyha kihagyod ezt a lépést.\nAhhoz hogy letudj tölteni, be kell állítanod a letöltési mappát az alkalmazás beállításában.
-Téma
+ Téma
Kérjük, válasszon egy megjelenítési témát.
-Források
+ Források
Válaszd ki a forrásokat ahonnan leszeretnél tölteni.
A választását később bármikor megváltoztathatja a bal oldali menüben található szerkesztés gomb segítségével.
-Minden készen áll!
+ Minden készen áll!
Készen is vagyunk!\nÉlvezd az alkalmazást.
diff --git a/app/src/main/res/values-it/array_preferences.xml b/app/src/main/res/values-it/array_preferences.xml
index b4b70ac19f..4127214385 100644
--- a/app/src/main/res/values-it/array_preferences.xml
+++ b/app/src/main/res/values-it/array_preferences.xml
@@ -38,7 +38,7 @@
100 caratteri
120 caratteri
Scarica tutte le pagine
- Trasmetti in streaming le pagine durante la lettura
+ Mostra le pagine mentre leggi
Alfabetico
Conteggio degli elementi (i più usati per primi)
Nessuno
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index c05c08cd88..57040cd86e 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -23,6 +23,7 @@
- File
- Files
+ Gruppi
Pausa
Ferma
Riprendi
@@ -33,6 +34,7 @@
Apri cartella
predefinito
vuoto
+ Nessuno
Migrazione API 29
Libreria
Coda
@@ -55,11 +57,11 @@
Si prega di consentire il permesso di scrittura
Cerca
Attiva/disattiva filtro preferito
- Attiva/disattiva filtro completato
Attiva/disattiva preferito
Nuovo gruppo
- Modifica ordine
- Modifica nome
+ Riordina
+ Rinomina
+ Conferma modifica
Annulla modifica
Il nuovo ordine personalizzato che stai per creare sovrascriverà quello precedente.
Ordina libri
@@ -303,9 +305,6 @@
Riprendere a leggere da dove l\'hai lasciato\?
Apri il cassetto
Chiudi il cassetto
- Come vorresti filtrare i libri completati\?
- Mostra solo completati
- Mostra solo i non completati
- Cancellare l\'elemento selezionato\?
- Cancellare %d elementi selezionati\?
@@ -437,13 +436,17 @@ Questo libro sarà ignorato.
Contenuto diviso
Tutti i libri
Per artista
- Per data di download
- Personalizzato
+ Per data di download (processato)
+ Gruppi personalizzati
[nessun artista]
-Titolo
+Ascendente
+ Discendente
+ Rimescola
+ Titolo
Artista
Pagine
- Data di download
+ Data download (processato)
+ Data download (completato)
Leggi data
Legge
Dimensione
@@ -452,15 +455,21 @@ Questo libro sarà ignorato.
Personalizzato
-invalido-
Libri
+ Data di caricamento
+ Filtri
+ Attiva/disattiva visualizzazione dei libri preferiti
+ Mostra solo libri finiti
+ Mostra solo libri non finiti
+Mostra gruppi
+ Mostra
+ Artisti
+ Gruppi
Trascina elemento
Appartiene alla libreria esterna
Libro in streaming
Visualizza sorgente nel browser
Segna/deseleziona come preferito
-Mostra artisti
- Mostra gruppi
- Mostra artisti e gruppi
- Hai già questo libro. Cercare altre fonti\?
+Hai già questo libro. Cercare altre fonti\?
Aggiornamenti
Verifica aggiornamenti
È disponibile un aggiornamento!
@@ -560,7 +569,7 @@ Questo libro sarà ignorato.
Attiva/disattiva il rimescolamento delle pagine
Mostra solo le pagine preferite
Mostra tutte le pagine
- Attiva/disattiva la visualizzazione della pagina preferita
+ Attiva/disattiva visualizzazione delle pagine preferite
Imposta come copertina
Cancella libro
Copiato nella cartella Downloads
diff --git a/app/src/main/res/values-it/strings_settings.xml b/app/src/main/res/values-it/strings_settings.xml
index 6d2c75f770..3fc612faae 100644
--- a/app/src/main/res/values-it/strings_settings.xml
+++ b/app/src/main/res/values-it/strings_settings.xml
@@ -11,6 +11,9 @@
Scorri fino al contenuto del tuo cuore.
Quantità per pagina
Numero di elementi per pagina nell\'elenco dei download. \nAttualmente: %s elementi
+ Pulsante vai in cima
+ Pulsante disattivato
+ Pulsante attivato
Ricerca avanzata
Ordine di selezione del metadata visualizzato
Conta i libri disponibili
@@ -35,7 +38,7 @@
Riduci il nome della cartella \nAttualmente: %s
Utilizzo della memoria
Spazio libero attuale: %.2f%% (tocca per i dettagli)
- Avvisa con memoria bassa
+ Avvisa con memoria bassa
Libreria esterna
Libreria esterna
Nessuna libreria esterna impostata
diff --git a/app/src/main/res/values-it/strings_slides.xml b/app/src/main/res/values-it/strings_slides.xml
index 8d2dbe0b0b..a2117e9023 100644
--- a/app/src/main/res/values-it/strings_slides.xml
+++ b/app/src/main/res/values-it/strings_slides.xml
@@ -1,19 +1,19 @@
-Benvenuto su Hentoid !
- Hentoid è un\'app di archiviazione \ndi Doujinshi e H-manga.
-Permessi
+ Benvenuto su Hentoid!
+ Hentoid è un app di archiviazione per\n& Doujinshi e H-manga.
+ Permessi
Dobbiamo chiederti l\'autorizzazione per archiviare i file sul tuo dispositivo.
-Libreria
+ Libreria
Hentoid richiede una cartella dedicata per salvare i vostri media.
Salta
Saltare\?
- Non potrai scaricare se salti questo passaggio. \nPer scaricare, dovrai impostare la cartella di download tramite le impostazioni dell\'app.
-Tema
+ Non potrai scaricare se salti questo passaggio.\nPer scaricare, dovrai impostare la cartella di download tramite le impostazioni dell\'app.
+ Tema
Scegli un tema di visualizzazione.
-Fonti
+ Fonti
Seleziona le fonti da cui scaricare.
- Puoi cambiare questa selezione in un\'altro momento utilizzando il pulsante di modifica nel menu a sinistra.
-Tutto pronto!
+ Puoi cambiare questa selezione in un altro momento utilizzando il pulsante di modifica nel menu a sinistra.
+ Tutto pronto!
Siamo pronti! \nGodetevi l\'applicazione.
diff --git a/app/src/main/res/values-nb-rNO/strings_slides.xml b/app/src/main/res/values-nb-rNO/strings_slides.xml
index a9c272bbcc..6d3f277311 100644
--- a/app/src/main/res/values-nb-rNO/strings_slides.xml
+++ b/app/src/main/res/values-nb-rNO/strings_slides.xml
@@ -1,19 +1,19 @@
-Velkommen til Hentoid.
+ Velkommen til Hentoid.
Hentoid is a Doujinshi \nog H-Manga-arkiveringsprogram
-Tilganger
+ Tilganger
Lagringstilgang er nødvendig for å lagre filer på enheten din.
-Bibliotek
+ Bibliotek
Hentoid krever at du har en egen mappe til lagring av media.
Hopp over
Hopp over\?
Du vil ikke kunne laste ned hvis du hopper over dette steget \nDet er mulig å sette opp en nedlastingsmappe i programinnstillingene
-Drakt
+ Drakt
Velg en visningsdrakt.
-Kilder
+ Kilder
Velg kilder å laste ned fra.
Du kan endre dette senere ved å bruke redigeringsknappen i menyen til venstre.
-Alt er satt opp.
+ Alt er satt opp.
Alt klart. \nNyt programmet.
diff --git a/app/src/main/res/values-pt-rBR/array_plug_reactions.xml b/app/src/main/res/values-pt-rBR/array_plug_reactions.xml
new file mode 100644
index 0000000000..d9c0b25e89
--- /dev/null
+++ b/app/src/main/res/values-pt-rBR/array_plug_reactions.xml
@@ -0,0 +1,16 @@
+
+
+yametee~~♡
+ pare, esse é… o buraco errado~
+ ughhh~
+ ewww ♡ ♡
+ não aqui~~♡
+ mau, menino mau~~
+ muito... apertado... ♡
+Eu gosto de lá~~
+ empurre-o ainda mais ~~~♡
+ ikuuuu~~
+ ahh ♡
+ ohhh~
+ me dê mais ♡
+
diff --git a/app/src/main/res/values-pt-rBR/strings_download_service.xml b/app/src/main/res/values-pt-rBR/strings_download_service.xml
new file mode 100644
index 0000000000..b6bcff40e0
--- /dev/null
+++ b/app/src/main/res/values-pt-rBR/strings_download_service.xml
@@ -0,0 +1,16 @@
+
+
+
+ - Download concluído
+ - %d downloads concluídos
+
+ Download concluído
+ Download cancelado
+ Download concluído, mas ocorreu um erro
+
+ - %1$d/%2$ditem adicionado à fila
+ - %1$d/%2$ditens adicionados à fila
+
+ Já baixado
+ Já na fila
+
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings_slides.xml b/app/src/main/res/values-pt-rBR/strings_slides.xml
new file mode 100644
index 0000000000..d53b6b5ed2
--- /dev/null
+++ b/app/src/main/res/values-pt-rBR/strings_slides.xml
@@ -0,0 +1,19 @@
+
+
+ Bem-vindo ao Hentoid!
+ Hentoid é um aplicativo de arquivamento Doujinshi\n& H-Manga.
+ Permissões
+ Precisamos pedir sua permissão para armazenar arquivos em seu dispositivo.
+ Biblioteca
+ Hentoid requer uma pasta dedicada para armazenar sua mídia.
+Pular
+Pular\?
+ Você não poderá fazer o download se pular esta etapa.\nln para fazer o download, você terá que definir a pasta de download nas configurações do aplicativo.
+ Tema
+ Escolha um tema de exibição.
+ Fontes
+Selecione as fontes para download
+ Você pode alterar essa seleção mais tarde a qualquer momento usando o botão de edição no menu à esquerda.
+ Tudo configurado!
+ Estamos definidos!\nAproveite o aplicativo.
+
diff --git a/app/src/main/res/values-pt-rBR/strings_tools.xml b/app/src/main/res/values-pt-rBR/strings_tools.xml
new file mode 100644
index 0000000000..a2f1cf4447
--- /dev/null
+++ b/app/src/main/res/values-pt-rBR/strings_tools.xml
@@ -0,0 +1,23 @@
+
+
+Ferramentas
+Importar / Exportar
+Exportar metadados
+Exportar livros Hentoid para um arquivo JSON
+Importar metadados
+Restaurar livros Hentoid exportados de um arquivo JSON
+Configurações
+Exportar configurações
+Exportar configurações para um arquivo JSON
+Importar configurações
+Restaurar configurações exportadas de um arquivo JSON
+Detector de duplicata
+Identificar e remover livros duplicados
+Gerenciamento de cache
+Limpar cache do navegador
+O cache do WebView foi limpo com sucesso
+Limpar cache do aplicativo
+O cache do aplicativo foi limpo com sucesso
+Registros do aplicativo
+Ver os registros mais recentes do aplicativo
+
diff --git a/app/src/main/res/values-ru/array_preferences.xml b/app/src/main/res/values-ru/array_preferences.xml
index c05e4ca329..9755a3392f 100644
--- a/app/src/main/res/values-ru/array_preferences.xml
+++ b/app/src/main/res/values-ru/array_preferences.xml
@@ -1,76 +1,96 @@
-Список (по умолчанию)
+
+ Список (по умолчанию)
Сетка
-Не уведомлять
+
+ Не уведомлять
когда заполнена на 90%
когда заполнена на 95%
когда заполнена на 98%
-500 мс (по умолчанию)
- 1 с
- 1.5 с
- 2 с
- 3 с
-Нет
-Вверху
+
+ 500 мс (по умолчанию)
+ 1 с
+ 1.5 с
+ 2 с
+ 3 с
+
+ Нет
+
+ Вверху
Внизу
Всегда спрашивать
-Без ограничения на размер
- 20 МБ
- 40 МБ
- 100 МБ
- 200 МБ
-Без ограничения на кол-во страниц
+
+ Без ограничения на размер
+ 20 МБ
+ 40 МБ
+ 100 МБ
+ 200 МБ
+
+ Без ограничения на кол-во страниц
60 страниц
120 страниц
200 страниц
500 страниц
-Автоматически (по умолчанию)
+
+ Автоматически (по умолчанию)
2 (медленный интернет)
10 (быстрый интернет и производительное устройство)
+
Название - ID
Художник - Название - ID
Название - Художник - ID
-Не сокращать
+
+ Не сокращать
60 символов
80 символов
100 символов
120 символов
-Загружать все страницы
+
+ Загружать все страницы
Передавать страницы потоком (стримить)
-По алфавиту
+
+ По алфавиту
Сначала наиболее используемые
-Нет
-Не добавлять в очередь
+
+ Нет
+
+ Не добавлять в очередь
Добавлять в очередь как неудачные загрузки
-Слева направо
+
+ Слева направо
Справа налево
Сверху вниз
-Выкл. (по умолчанию)
+
+ Выкл. (по умолчанию)
Маленькие
Обычные
Большие
-1 страница (по умолчанию)
+
+ 1 страница (по умолчанию)
2 страницы
5 страниц
Все страницы
-500 мс
- 1 с
+
+ 500 мс
+ 1 с
2 с (по умолчанию)
- 4 с
- 8 с
- 16 с
-По размеру экрана (по умолчанию)
+ 4 с
+ 8 с
+ 16 с
+
+ По размеру экрана (по умолчанию)
Заполнить экран
Растянуть на весь экран
-Чёткая отрисовка (по умолчанию)
+
+ Чёткая отрисовка (по умолчанию)
Плавная отрисовка
-Не ограничивать
-
-
-Сразу
- 10 с
+
+ Не ограничивать
+
+ Сразу
+ 10 с
30 с (по умолчанию)
- 1 мин.
+ 1 мин.
2 мин.
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/array_splash_quotes.xml b/app/src/main/res/values-ru/array_splash_quotes.xml
index 98a5aeb355..ee8664d88d 100644
--- a/app/src/main/res/values-ru/array_splash_quotes.xml
+++ b/app/src/main/res/values-ru/array_splash_quotes.xml
@@ -2,7 +2,6 @@
- искусство в приложении
- - опять _эти_ книги?
- опять эти книги\?
- приложение для тренировки руки
- пятиминутное упражнение для руки
@@ -23,7 +22,6 @@
- Папочка....
- и вверх, и вниз…
- ВОТ ЭТО КОЛБАСА
- - Ты серьёзно дрочишь один?
- Ты серьёзно дрочишь один\?
- Пригласи друзей ;3
- Попроси девушку- а, стоп…
@@ -32,20 +30,17 @@
- Эректус
- милая палочка, анон
- милый, пора вертеть колбаску
- - Колбаска *БРРРРРРР*
- Колбаска *БРРРРРРР*
- Прыгающие дыньки
- Cолёно, но вкусно
- Есть дырка — есть и способ
- входи в меня полностью
- - а? это что, член?
- а\? это что, член\?
- Фууу, убери это
- то ещё приложеньице
- используй смазку, брат
- https://youtu.be/dQw4w9WgXcQ
- книги мои книги
- - Стоп, не манга?
- Стоп, не манга\?
- Дьявольски соблазнительно
- Я думал, мы будем есть моллюсков на пару
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 04e2d9bb80..be6214aacf 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -1,9 +1,12 @@
-Добро пожаловать
-Да
-Нет
- ОК
+
+ Добро пожаловать
+
+ Да
+
+ Нет
+ ОК
Вкл.
Выкл.
Выбрать всё
@@ -27,7 +30,7 @@
- файла
- файлов
- Отменить
+ Группы
Приостановить
Остановить
Возобновить
@@ -37,14 +40,20 @@
Сервис уже запущен
Открыть папку
по умолчанию
-Переход на API 29
+ пусто
+ Никак
+
+
+ Переход на API 29
Библиотека
Очередь
Настройки
О Hentoid
Код-пароль
Расширенный поиск
-Hentoid v12\nОбновление управления файлами
+
+
+ Hentoid v12\nОбновление управления файлами
Hentoid обновляет вашу коллекцию.\nЭто может занять несколько минут.
Расположение папки Hentoid\n%s
Папка Hentoid
@@ -57,30 +66,31 @@
Импорт очереди…
Пожалуйста, выберите существующую папку Hentoid. Её расположение показано на экране.
Пожалуйста, подтвердите разрешение на запись
-Поиск
+
+
+ Поиск
Показать избранное
- Показать завершённые
Избранные
Новая группа
- Редактировать порядок
- Редактировать название
+ Пересортировать
+ Переименовать
+ Подтвердить редактирование
Отменить редактирование
Новая пользовательская сортировка заменит предыдущую.
Сортировать книги
Удалить
- Удалить элементы
Переместить в группу…
Открыть родительскую папку
Загрузить снова
Загрузить с нуля
- Переключиться на потоковую передачу
+ Передавать потоком
Загрузить
Выбрать всё
Поделиться
Поделиться ссылкой на книгу и её названием через стороннее приложение
- Ссылка на книгу
В архив
Заархивировать книгу в ZIP
+ Ссылка на книгу
Название, художник, тэг или ID книги
Поделиться через:
Настройки
@@ -90,10 +100,12 @@
Заканчивается память на устройстве!
Исправить
Просмотреть очередь
-Загрузчик
+
+
+ Загрузчик
В процессе
Ошибки
-
+
- Отменить выбранный элемент\?
- Отменить выбранные элементы\?
- Отменить выбранные элементы\?
@@ -101,10 +113,7 @@
Инвертировать порядок
Загрузить всё снова
Отменить все ошибки
- Отменено
В начало
- Вверх
- Вниз
В конец
Очередь приостановлена
Подготовка загрузки
@@ -136,6 +145,7 @@
Подготовка папки загрузок
Подготовка цепочки загрузок
Сохранение очереди
+ Очистка в процессе…
Начата цепочка загрузок!
Вы можете отменять загрузки свайпом влево
Возникла проблема при повторной загрузке — отменено
@@ -160,11 +170,13 @@
- (%d ошибок)
[попытка %1$d из %2$d]
- %d Кб/с
- %1$d%2$s МБ, %3$d Кб/c
+ %d Кб/с
+ %1$d%2$s МБ, %3$d Кб/c
Загрузка не удалась
Невозможно загрузить %1$s: не удалось создать папку %2$s. Пожалуйста, проверьте папку Hentoid и повторите загрузку с помощью кнопки \'!\'.
-Сеттинг: %s
+
+
+ Сеттинг: %s
Художник: %s
- (%d пропущена)
@@ -191,11 +203,8 @@
Книга помечена заблокированным тэгом
Неизвестно
-
-
-
-
-Обновить библиотеку
+
+ Обновить библиотеку
Автоматически обновить библиотеку, сканируя файлы и папки снова.
Опции очистки
Библиотека Hentoid
@@ -204,10 +213,11 @@
Удалить папки, в которых…
Отсутствует JSON-файл (рекомендуется)
Отсутствуют изображения
- JSON-файл не читаем (не рекомендуется)
или
-Удалить всё, кроме избранных
- %1$d из %2$d %3$s
+
+
+ Удалить всё, кроме избранных
+ %1$d из %2$d %3$s
- %1$d книга из %2$d
- %1$d книги из %2$d
@@ -218,7 +228,9 @@
- %1$d группы из %2$d
- %1$d групп из %2$d
-Импорт метаданных
+
+
+ Импорт метаданных
Добавить импортированные элементы
Заменить импортированными элементами
Выбор файла для импорта
@@ -256,7 +268,9 @@
- %d закладок успешно импортировано
Выберите папку
-Экспорт метаданных
+
+
+ Экспорт метаданных
- Экспорт библиотеки (%d книга)
- Экспорт библиотеки (%d книги)
@@ -279,7 +293,8 @@
ЗАПУСТИТЬ ЭКСПОРТ
Импорт настроек
Успешно импортировано
-Открыть настройки приложения
+
+
Вы должны предоставить разрешения перед тем, как продолжить
Вы должны вручную предоставить разрешения перед тем, как продолжить
Импорт отменён
@@ -295,10 +310,7 @@
Импорт библиотеки
Импорт начинается…
Импорт библиотеки
- Импортирую, пожалуйста, подождите…
- Обновляю, пожалуйста, подождите…
Импорт завершён
- Импорт завершён
- Успешно импортировано: %s
- Успешно импортировано: %s
@@ -309,7 +321,9 @@
- Не удалось импортировать: %s
- Не удалось импортировать: %s
-Использование памяти
+
+
+ Использование памяти
Всего: %s
Свободно: %s
Hentoid (основное хранилище): %s
@@ -319,7 +333,9 @@
Источник
Книги
Размер
-Последние логи
+
+
+ Последние логи
Открыть лог
Читать лог
Поделиться логом
@@ -328,24 +344,26 @@
Обновление/импорт основной библиотеки
Очистка основной библиотеки
Миграция библиотеки с Hentoid 1.11
-У вас нет прав на запись в этой папке, попробуйте другую.
+
+
+ У вас нет прав на запись в этой папке, попробуйте другую.
Ошибка создания папки.
Ошибка открытия файла: не найдено подходящего приложения
Ошибка открытия ссылки: не обнаружен браузер
- Ошибка отправки архива: не найдено подходящего приложения
-Попытаться загрузить снова\?
+
+
+ Попытаться загрузить снова\?
Всего изображений в загрузке: %1$d\nИзображений без ошибок: %2$d\nИзображений с ошибками: %3$d
Первая ошибка: %s
ЗАГРУЗИТЬ СНОВА
Открыть лог ошибок
Поделиться логами ошибок
Генерирую файл логов…
-Возобновить чтение\?
+
+
+ Возобновить чтение\?
Открыть меню
Закрыть меню
- Как бы вы хотели обращаться с дочитанными книгами?
- Показывать только дочитанные
- Показывать только недочитанные
- Удалить %d выбранный элемент\?
- Удалить %d выбранных элемента\?
@@ -397,7 +415,6 @@
Загружаю~
Нет результатов :(
Тут такого нет, иди, скачай это.
- Упаковываю файлы.\nПожалуйста, подождите…
Поиск \'%s\' в…
ID: \'%s\'
@@ -410,11 +427,10 @@
- %1$d результата из %2$d
- %1$d результатов из %2$d
- Разрешения были сброшены.\nПриложение перезапускается.
- \"Изменения будут применены после перезапуска Hentoid\"
+ Изменения будут применены после перезапуска Hentoid
- Загрузить эту книгу снова\?
- - Загрузить эту книгу снова\?
+ - Загрузить эти книги снова\?
- Загрузить эти книги снова\?
@@ -442,7 +458,6 @@
- %d книги уже стримятся или находятся во внешней библиотеке\nЭти книги будут пропущены.
- %d книг уже стримятся или находятся во внешней библиотеке\nЭти книги будут пропущены.
- Книга будет удалена…
Ищите по чему угодно: названию, художнику, тэгам\nи даже ID книги!
Вы можете проводить пальцем по номерам страниц
@@ -485,7 +500,9 @@
- Удалить группы и книги
Нечего удалять — включите опцию \"Разрешить удалять внешний контент\"
-Совместить
+
+
+ Совместить
Совмещаю книги…
Разделяю книгу…
Разделить
@@ -496,15 +513,23 @@
Создать главы
Успешно совмещено
Успешно разделено
-Все книги
+
+
+ Все книги
По художникам
По дате загрузки
- Своё
+ Свои группы
[без художника]
-По названиям
+
+
+ По возрастающей
+ По убывающей
+ Пересортировать
+ По названиям
По художникам
По страницам
- По дате загрузки
+ По дате начала загрузки
+ По дате завершения загрузки
По дате чтения
По прочтениям
По размеру
@@ -513,16 +538,30 @@
Своё
-неверно-
По кол-ву книг
-Переместить элемент
+ По дате начала загрузки
+ Фильтры
+ Отображение избранных книг
+ Показывать только завершённое
+ Показывать не только завершённое
+
+
+ Показать группы
+ Показать
+ Художники
+ Группы
+
+
+ Переместить элемент
Находится во внешней библиотеке
Книга передаётся потоком
Просмотреть источник в браузере
Добавить/убрать из избранного
-Художники
- Группы
- Художники и группы
+
+
У вас уже есть эта книга. Искать в других источниках\?
-Обновления
+
+
+ Обновления
Проверка обновлений
Доступно обновление!
Нажмите для загрузки
@@ -531,36 +570,38 @@
Загружается обновление
Обновление готово!
Нажмите для установки
- Произошла ошибка при сравнении зависимостей обновления.\nПожалуйста, попробуйте позднее.
- Ошибка сети, пожалуйста, попробуйте позднее.
- Не удалось найти файл обновления на сервере.
- Проверка обновлений: Нет новых версий.
-Учтите, что загрузки могут не работать на этом сайте.
+
+
+ Учтите, что загрузки могут не работать на этом сайте.
Кнопка загрузки пока что не работает на этом сайте.
Этот сайт сейчас работает нестабильно. Приложение не может ничего с этим сделать. Пожалуйста, попробуйте позднее.
Этот сайт сейчас недоступен. Приложение не может ничего с этим сделать. Пожалуйста, попробуйте позднее.
Команда разработчиков Hentoid работает над исправлением. Мы уведомим вас, когда новая версия приложения будет доступна.
Исправление доступно в последней версии приложения. Пожалуйста, установите обновление.
-Введите код-пароль
- Код-пароль приложения
+
+
+ Введите код-пароль
Блокировка включена
Блокировка выключена
Изменить код-пароль
Код-пароль изменён
- Код-пароль верный!
Введите текущий код-пароль
Введите новый код-пароль
Подтвердите новый код-пароль
Блокировка кодом-паролем предотвращает использование этого приложения без предварительной авторизации с помощью 4-значного PIN-кода
Блокировать при\nповторном открытии
-Hentoid — это приложение для скачивания додзинси и хентай-манги. Hentoid изначально был создан Сесаром Арасаки.
+
+
+ Hentoid — это приложение для скачивания додзинси и хентай-манги. Hentoid изначально был создан Сесаром Арасаки.
ЗАЯВЛЕНИЕ О КОНФИДЕНЦИАЛЬНОСТИ ПЕРСОНАЛЬНЫХ ДАННЫХ\n\nHentoid — это бесплатное програмное обеспечение с открытым исходным кодом, разрабатываемое сообществом Hentoid (присоединяйтесь к нам в Discord по ссылке ниже)
Hentoid собирает следующие персональные данные и делится ими с Google для обработки с помощью библиотеки Firebase:\n - Идентификатор сессии\n - IP-адрес
Вышеупомянутые данные удаляются как из действующей, так и из запасной систем Firebase в течение 180 дней (подробности доступны на https://firebase.google.com/support/privacy/)\n\nНикакие другие персональные данные не собираются и не передаются третьим лицам.
Показать лицензии
- Версия Hentoid: %1$s (%2$d)
- Версия Chrome: %1$d
-Назад к последней странице галереи
+ Версия Hentoid: %1$s (%2$d)
+ Версия Chrome: %1$d
+
+
+ Назад к последней странице галереи
Назад к галерее
Назад
Вперёд
@@ -581,22 +622,25 @@
Домашняя страница
Не удалось начать стриминг: %s
Отсутствует разрешение на запись — невозможно использовать загрузчик
-Hentoid не может работать с FAKKU без их аккаунта. Пожалуйста, создайте его или используйте существующий.
- Эта книга не может быть просмотрена с вашим аккаунтом FAKKU.
+
Пожалуйста, создайте или используйте существующий аккаунт exHentai.
Неверные данные, пожалуйста, повторите попытку входа
Авторизация не завершена, пожалуйста, повторите попытку входа. Если проблема сохраняется, попробуйте посетить сайт из другой страны или создайте новый аккаунт.
-Любое
+
+
+ Любое
Исключить
Ищем %s
Искать %s
-Художник (%1$d)
+
+ Художник (%1$d)
Тэг (%1$d)
Персонаж (%1$d)
Сеттинг (%1$d)
Язык (%1$d)
Источник (%1$d)
-художника
+
+ художника
тэг
персонажа
сеттинг
@@ -613,11 +657,11 @@
- Найдено %s результатов
Выберите фильтр
-Выполняется обслуживание
-Не удалось загрузить книгу: Не найдено ни одного изображения
+
+
+ Не удалось загрузить книгу: Не найдено ни одного изображения
Загрузка: %1$d из %2$d
Загрузка изображения: %d%%
- Изображение не найдено
%1$d x %2$d (масштаб %3$.0f%%) - %4$s
Выберите направление чтения
Слева направо
@@ -632,12 +676,9 @@
Показывать все страницы
Показ избранных страниц
Установить как обложку
- Меню страницы
- Открыть настройки страницы
Удалить книгу
Скопировано в Загрузки
Не удалось скопировать в Загрузки
- Удалить эту книгу\?
Удалить эту страницу\?
Информация
Предыдущая книга
@@ -658,10 +699,6 @@
Настройки книги
Настройки приложения
Использовать настройки приложения (%s)
- Подробности о странице
- Подробности о книге
- Избранное (страница)
- Избранное (книга)
Страница добавлена в избранное
Страница удалена из избранного
Книга добавлена в избранное
@@ -690,14 +727,20 @@
Начало слайдшоу (задержка в %.1f с)
Слайдшоу остановлено
Удалить все главы?
-Что нового\?
+
+
+ Что нового\?
Что нового\?*
Что нового\?
Доступна новая версия (%1$s)
Обновление завершено!
Загрузка начата
-Меню редактирования
-Поиск дубликатов
+
+
+ Меню редактирования
+
+
+ Поиск дубликатов
Название
Обложка
Художник
@@ -723,7 +766,6 @@
Обложка: нет данных
Художник: %.0f%%
Художник: нет данных
- Применить (осталось %d)
Применить
Начинаю определение дубликатов
Определение дубликатов завершено
@@ -742,18 +784,20 @@
Дубликатов не обнаружено
Оставить
Удалить
-Внимание: дубликат!
+
+
+ Внимание: дубликат!
Книга, которую вы собираетесь загрузить, может быть дубликатом книги, которая у вас уже есть.
Книга, страницы которой вы собираетесь загрузить, может быть не той, которая у вас есть.
- В библиотеке
- В очереди
Всегда загружать и больше не спрашивать
Никогда больше не добавлять страницы из потенциальных дубликатов
Схожесть: %.0f%%
Загрузить книгу
Загрузить дополнительные страницы
Пожалуйста, подождите — идёт индексирование
-Архивирование контента
+
+
+ Архивирование контента
Удаление контента
Удаление завершено
@@ -763,21 +807,27 @@
Удаление не удалось
Не удалось удалить по крайней мере одну книгу
-Запуск
+
+
+ Запуск
Запускается…
Запуск завершён
-Сегодня
+
+
+ Сегодня
На неделе
За этот месяц
За последние 60 дней
За последний год
Давно
Без группы
-Светлая
+
+
+ Светлая
Тёмно-красная
Тёмная
-
+
английский
китайский
японский
@@ -796,17 +846,13 @@
турецкий
чешский
-
-
-
-
-Выберите ваш файловый менеджер
- Стандартное разрешение
- б.
- КБ
- МБ
- ГБ
- ТБ
- ПБ
- ЭБ
+
+ Выберите ваш файловый менеджер
+ б.
+ КБ
+ МБ
+ ГБ
+ ТБ
+ ПБ
+ ЭБ
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings_download_service.xml b/app/src/main/res/values-ru/strings_download_service.xml
index da3431a890..b384674b5d 100644
--- a/app/src/main/res/values-ru/strings_download_service.xml
+++ b/app/src/main/res/values-ru/strings_download_service.xml
@@ -7,11 +7,7 @@
Загрузка завершена
Загрузка отменена
- Загрузка пропущена
- Загрузка приостановлена
Загрузка завершена, но с ошибками
- Необрабатываемая ошибка при чтении ссылок на изображения
- Загружается
- %1$d элемент из %2$d добавлен в очередь
- %1$d элемента из %2$d добавлены в очередь
diff --git a/app/src/main/res/values-ru/strings_settings.xml b/app/src/main/res/values-ru/strings_settings.xml
index 707baae8fb..0007dbcc71 100644
--- a/app/src/main/res/values-ru/strings_settings.xml
+++ b/app/src/main/res/values-ru/strings_settings.xml
@@ -1,6 +1,8 @@
-Интерфейс и навигация
+
+
+ Интерфейс и навигация
Библиотека
Управление источниками
Включайте, выключайте и реорганизовывайте источники, показываемые в левом меню
@@ -11,6 +13,9 @@
Прокручивайте, сколько вашей душе угодно
Кол-во элементов на странице
Определяет, сколько элементов отображается на одной странице в списке загрузок\nСейчас: %s элементов
+ Кнопка перехода наверх
+ Отключена
+ Включена
Расширенный поиск
Способ сортировки отображаемых метаданных
Отображать число доступных книг
@@ -19,7 +24,9 @@
Тема приложения
Цветовая схема
Текущая тема: %s
-Хранилище
+
+
+ Хранилище
Библиотека Hentoid
Выбор папки загрузок
Выберите, где Hentoid будет хранить ваши загрузки
@@ -54,7 +61,9 @@
- Удалить %d книги\?
- Удалить %d книг\?
-Браузер
+
+
+ Браузер
Поведение
Восстанавливать последнюю страницу
Сайты будут открываться со своих главных страниц
@@ -62,6 +71,7 @@
Загрузка
Быстрая загрузка
Не открывая книгу, нажмите на неё для загрузки
+ Заблокированные на nhentai книги отображаются…
Отображаются с размытием
Не отображаются
Порог долгого нажатия для быстрой загрузки
@@ -86,7 +96,9 @@
Уровень масштаба при загрузке страницы\nСейчас: %s%%
Сторонний DNS
DNS через HTTPS
-Загрузчик
+
+
+ Загрузчик
Автоматически начинать очередь загрузки
Очередь загрузки начнётся как только загрузка завершится
Очередь загрузки запустится только вручную
@@ -109,12 +121,11 @@
Заблокированные тэги
Книги с определёнными тэгами не будут загружаться\nПодсказка: тэги разделяются запятыми
Плохие тэги нужно…
- Предпочитать загрузку WebP
- Загружать изображения в других форматах (PNG, JPG)
- Загружать WebP, когда возможно
Кол-во параллельных загрузок
Число изображений, загружающихся одновременно\nСейчас: %s
-Конфиденциальность
+
+
+ Конфиденциальность
Предпросмотр в списке недавних приложений
В списке недавних приложений не отображается, что открыто в приложении\nВключите, если хотите делать скриншоты приложения
Содержимое экрана не скрывается в списке недавних приложений
@@ -124,7 +135,9 @@
Аналитика Firebase
Сбор аналитики выключен.\nВключите его, чтобы мы могли улучшать приложение, отслеживая сбои и ошибки в нём
Сбор аналитики включён.\nСпасибо, что помогаете нам улучшить Hentoid!
-Обновления
+
+
+ Обновления
Проверить обновления вручную
Сделать проверку прямо сейчас
Проверяю обновления…
@@ -133,7 +146,9 @@
Автоматические обновления
Вы также можете включить автоматическую проверку обновлений по мобильной сети
Это не отключает возможность ручной проверки обновлений
-Просмотр изображений
+
+
+ Просмотр изображений
Управление
Режим обзора
Режим перелистывания страниц
diff --git a/app/src/main/res/values-ru/strings_slides.xml b/app/src/main/res/values-ru/strings_slides.xml
index 3d2c8dc489..beea88a66b 100644
--- a/app/src/main/res/values-ru/strings_slides.xml
+++ b/app/src/main/res/values-ru/strings_slides.xml
@@ -1,19 +1,19 @@
-Добро пожаловать в Hentoid!
+ Добро пожаловать в Hentoid!
Hentoid — это приложение для скачивания\nдодзинси и хентай-манги.
-Разрешения
+ Разрешения
Нам необходимо ваше разрешение, чтобы хранить файлы на устройстве.
-Библиотека
+ Библиотека
Hentoid необходима отдельная папка для хранения ваших файлов.
Пропустить
Пропустить\?
Вы не сможете ничего загрузить, если пропустите этот шаг.\nДля загрузки вам понадобится выбрать папку загрузок в настройках приложения.
-Тема
+ Тема
Выберите тему.
-Источники
+ Источники
Выберите источники для загрузок.
Вы сможете изменить этот выбор позднее в любой момент, нажав на \'Изменить\' в меню слева.
-Вот и всё!
+ Вот и всё!
Всё готово!\nНаслаждайтесь приложением.
diff --git a/app/src/main/res/values-uk/array_plug_reactions.xml b/app/src/main/res/values-uk/array_plug_reactions.xml
new file mode 100644
index 0000000000..583ba6fbde
--- /dev/null
+++ b/app/src/main/res/values-uk/array_plug_reactions.xml
@@ -0,0 +1,16 @@
+
+
+ ямете~~♡
+ стій, це… не та дірка~
+ ах~
+ фууу ♡ ♡
+ не сюди~~♡
+ поганий, поганий хлопчик~~
+ так… тісно…♡
+ мені так приємно там~~
+ глибше~~~♡
+ ікуууу~~
+ а-ах ♡
+ ооох~
+ ще ♡
+
diff --git a/app/src/main/res/values-uk/array_preferences.xml b/app/src/main/res/values-uk/array_preferences.xml
new file mode 100644
index 0000000000..9876c82256
--- /dev/null
+++ b/app/src/main/res/values-uk/array_preferences.xml
@@ -0,0 +1,96 @@
+
+
+
+ Список (за замовчуванням)
+ Сітка
+
+ Не повідомляти
+ коли заповнена на 90%
+ коли заповнена на 95%
+ коли заповнена на 98%
+
+ 500 мс (за замовчуванням)
+ 1 с
+ 1.5 с
+ 2 с
+ 3 с
+
+ Ніякий
+
+ Вгорі
+ Внизу
+ Завжди запитувати
+
+ Без обмеження на розмір
+ 20 МБ
+ 40 МБ
+ 100 МБ
+ 200 МБ
+
+ Без обмеження на кількість сторінок
+ 60 сторінок
+ 120 сторінок
+ 200 сторінок
+ 500 сторінок
+
+ Автоматично (за замовчуванням)
+ 2 (повільний інтернет)
+ 10 (швидкий інтернет та продуктивний пристрій)
+
+ Назва - ID
+ Художник - Назва - ID
+ Назва - Художник - ID
+
+ Не скорочувати
+ 60 символів
+ 80 символів
+ 100 символів
+ 120 символів
+
+ Завантажувати усі сторінки
+ Передавати сторінки потоком (стріміти)
+
+ За алфавітом
+ Спочатку найбільш використовувані
+
+ Ніякий
+
+ Не додавати в чергу
+ Додавати у чергу як невдалі завантаження
+
+ Зліва направо
+ Справа наліво
+ Зверху вниз
+
+ Вимк. (за замовчуванням)
+ Маленькі
+ Звичайні
+ Великі
+
+ 1 сторінка (за замовчуванням)
+ 2 сторінки
+ 5 сторінок
+ Всі сторінки
+
+ 500 мс
+ 1 с
+ 2 с (за замовчуванням)
+ 4 с
+ 8 с
+ 16 с
+
+ За розміром екрану (за замовчуванням)
+ Заповнити екран
+ Розтягнути на весь екран
+
+ Чітке відмальовування (за замовчуванням)
+ Плавне відмальовування
+
+ Не обмежувати
+
+ Відразу
+ 10 с
+ 30 с (за замовчуванням)
+ 1 хв.
+ 2 хв.
+
diff --git a/app/src/main/res/values-uk/array_splash_quotes.xml b/app/src/main/res/values-uk/array_splash_quotes.xml
new file mode 100644
index 0000000000..46739ad764
--- /dev/null
+++ b/app/src/main/res/values-uk/array_splash_quotes.xml
@@ -0,0 +1,49 @@
+
+
+
+ - мистецтво у додатку
+ - знову ці книги\?
+ - додаток для тренування руки
+ - п\'ятихвилинна вправа для руки
+ - найкращий друг твоєї руки
+ - додаток вмик, штани вимк
+ - стеж за околицями, аноне
+ - будь ласка, будь ніжний, сенпаю~~
+ - поміняй партнера
+ - день рук
+ - цілься
+ - точне попадання
+ - сасіська
+ - 21 палець
+ - стриб-стриб :3
+ - ПОЗАДУ ТЕБЕ
+ - БАКА НЕ ШЛЬОПАЙ МЕНЕ
+ - Завантажуємо Збоченця
+ - Татко....
+ - і вгору, і вниз…
+ - ОСЬ ЦЕ КОВБАСА
+ - Ти серйозно дрочиш один\?
+ - Запроси друзів ;3
+ - Попроси дівчину- а, стоп…
+ - Мила дівчинка, стоп ЦЕ Ж ТРАП
+ - Стріляй далеко
+ - Еректус
+ - мила паличка, аноне
+ - милий, час крутити ковбаску
+ - Ковбаска *БРРРРРРР*
+ - Стрибаючі диньки
+ - Солено, але смачно
+ - Є дірка – є і спосіб
+ - входь у мене повністю
+ - а\? це що, член\?
+ - Фууу, прибери це
+ - тей ще додаточек
+ - використовуй мастило, брате
+ - https://youtu.be/dQw4w9WgXcQ
+ - книги мої книги
+ - Стоп, не манґа\?
+ - Диявольськи спокусливо
+ - Я думав, ми будемо їсти молюсків на пару
+ - Сподіваюся, ви приготувалися до незабутнього обіду
+
+
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000000..1f06bb9c4c
--- /dev/null
+++ b/app/src/main/res/values-uk/strings.xml
@@ -0,0 +1,857 @@
+
+
+
+ Ласкаво просимо
+
+ Так
+
+ Ні
+ ОК
+ Вмик.
+ Вимк.
+ Вибрати все
+ Зняти виділення
+ Перейти до сторінки…
+ Підтвердити
+ Будь ласка, зачекайте…
+
+ - книга
+ - книги
+ - книг
+
+
+ - сторінка
+ - сторінки
+ - сторінок
+
+
+ - файл
+ - файла
+ - файлів
+
+ Групи
+ Призупинити
+ Зупинити
+ Відновити
+ Скасувати
+ Скасувати все
+ Завершено
+ Сервіс вже запущений
+ Відкрити папку
+ за замовчуванням
+ порожньо
+ Ніяк
+
+
+ Перехід на API 29
+ Бібліотека
+ Черга
+ Налаштування
+ Про Hentoid
+ Код-пароль
+ Розширений пошук
+
+
+ Hentoid v12\nОновлення керування файлами
+ Hentoid оновлює вашу колекцію.\nЦе може зайняти кілька хвилин.
+ Розташування папки Hentoid\n%s
+ Папка Hentoid
+ Зовнішня папка
+ Вибрати папку Hentoid
+ Вибрати зовнішню папку
+ Сканую папки сайтів
+ Сканую бібліотеку…
+ Сканування бібліотеки (%1$d/%2$d)
+ Імпорт черги…
+ Будь ласка, виберіть існуючу папку Hentoid. Її розташування показано на екрані.
+ Будь ласка, підтвердіть дозвіл на запис
+
+
+ Пошук
+ Показати обране
+ Обране
+ Нова група
+ Пересортувати
+ Перейменувати
+ Підтвердити редагування
+ Скасувати редагування
+ Нове користувальницьке сортування замінить попереднє.
+ Сортувати книги
+ Видалити
+ Перемістити до групи…
+ Відкрити батьківську папку
+ Завантажити знову
+ Завантажити з нуля
+ Передавати потоком
+ Завантажити
+ Вибрати все
+ Поділитися
+ Поділитися лінком на книгу та її назвою через сторонній додаток
+ В архів
+ Заархівувати книгу в ZIP
+ Лінк на книгу
+ Назва, художник, теґ чи ID книги
+ Поділитися через:
+ Налаштування
+ Налаштування додатка
+ Налаштування книги
+ Дозволи на роботу з файлами були втрачені
+ Закінчується пам\'ять на пристрої!
+ Виправити
+ Переглянути чергу
+
+
+ Завантажувач
+ В процесі
+ Помилки
+
+ - Скасувати вибраний елемент\?
+ - Скасувати вибрані елементи\?
+ - Скасувати вибрані елементи\?
+
+ Інвертувати порядок
+ Завантажити все знову
+ Скасувати всі помилки
+ В початок
+ В кінець
+ Черга призупинена
+ Підготовка завантаження
+ Завантажується: %s
+ Статистика помилок
+ Показати докладну статистику про помилки
+ Книга ще не існує
+ Скасувати все у черзі\?
+
+ - Скасувати %d книгу з помилками\?
+ - Скасувати %d книги з помилками\?
+ - Скасувати %d книг із помилками\?
+
+
+ - Завантажити %d елемент знову\?
+ - Завантажити %d елементи знову\?
+ - Завантажити %d елементів знову\?
+
+ Завантаження неможливе — Немає мережі
+ Завантаження неможливе — Немає підключення до Wi-Fi
+ Завантаження неможливе — Пам\'ять майже заповнена (залишилося %s). Звільніть місце або змініть папку для завантаження
+ Завантаження неможливе — Немає папки завантажень. Виберіть її в налаштуваннях додатка
+ Завантаження неможливе — Папки завантажень не знайдено. Виберіть її в налаштуваннях додатка
+ Завантаження неможливе — Немає доступу до папки завантажень. Виберіть її знову в налаштуваннях додатка
+ Завантаження неможливе — Застаріли Cookie або авторизація. Оновіть їх через повідомлення
+ Ініціалізація
+ Підготовка зображень
+ Завантаження зображень
+ Підготовка папки завантажень
+ Підготовка ланцюжка завантажень
+ Збереження черги
+ Очищення у процесі…
+ Розпочато ланцюжок завантажень!
+ Ви можете скасовувати завантаження свайпом вліво
+ Виникла проблема при повторному завантаженні — скасовано
+
+ - %1$d елемент із %2$d буде завантажено знову
+ - %1$d елементи з %2$d будуть завантажені знову
+ - %1$d елементів із %2$d будуть завантажені знову
+
+ Додати в початок
+ Додати в кінець
+ Користувальницькі дії
+ Необхідна дія користувача
+ Натисніть, щоб відновити завантаження
+ Будь ласка, не закривайте це вікно, поки ваше завантаження відновлюється
+ JSON черги успішно збережено
+ Не вдалося зберегти JSON очереди
+ приблизно %.1f МБ
+ %1$s/%2$d оброблено
+
+ - (%d помилка)
+ - (%d помилки)
+ - (%d помилок)
+
+ [спроба %1$d/%2$d]
+ %d Кб/с
+ %1$d%2$s МБ, %3$d Кб/c
+ Завантаження не вдалося
+ Неможливо завантажити %1$s: не вдалося створити папку %2$s. Будь ласка, перевірте папку Hentoid та повторіть завантаження за допомогою кнопки \'!\'.
+
+
+ Сеттинг: %s
+ Художник: %s
+
+ - (%d пропущено)
+ - (%d пропущено)
+ - (%d пропущено)
+
+ Сторінок: %1$s%2$s
+
+ - %1$d сторінка / %2$.1f МБ
+ - %1$d сторінки / %2$.1f МБ
+ - %1$d сторінок / %2$.1f МБ
+
+ %.0f МБ
+ Вибрані книги завантажуються знову
+ Обробка
+ Мережа
+ Обмін даними
+ Капча
+ Обробка зображення
+ Досягнуто ліміт завантажень або пропускної спроможності
+ Немає акаунта або недостатньо дозволів
+ Не виявлено локального файлу після імпорту
+ Книга пропущена через обмеження на розмір завантажень по Wi-Fi
+ Книга позначена заблокованим теґом
+ Невідомо
+
+
+ Оновити бібліотеку
+ Автоматично оновлювати бібліотеку, скануючи файли і папки знову.
+ Опції очищення
+ Бібліотека Hentoid
+ Зовнішня бібліотека
+ Перейменувати всі папки відповідно до поточних параметрів назв і максимальної довжини
+ Видалити папки, в яких…
+ Немає JSON-файлу (рекомендується)
+ Немає зображень
+ або
+
+
+ Видалити все, окрім обраних
+ %1$d/%2$d %3$s
+
+ - %1$d книга з %2$d
+ - %1$d книги з %2$d
+ - %1$d книг із %2$d
+
+
+ - %1$d група з %2$d
+ - %1$d групи з %2$d
+ - %1$d груп із %2$d
+
+
+
+ Імпорт метаданих
+ Додати імпортовані елементи
+ Замінити імпортованими елементами
+ Вибір файлу для імпорту
+ Неможливо обробити файл %s
+
+ - Імпорт бібліотеки (%d книга)
+ - Імпорт бібліотеки (%d книги)
+ - Імпорт бібліотеки (%d книг)
+
+
+ - Імпорт черги (%d книга)
+ - Імпорт черги (%d книги)
+ - Імпорт черги (%d книг)
+
+
+ - Імпорт груп користувача (%d група)
+ - Імпорт груп користувача (%d групи)
+ - Імпорт груп користувача (%d груп)
+
+
+ - Імпорт закладок (%d елемент)
+ - Імпорт закладок (%d елементи)
+ - Імпорт закладок (%d елементів)
+
+ Якщо папок із зображеннями не виявиться, імпортовані книги будуть відображені як порожні завантаження. Вам доведеться завантажити їх в Черга > Помилки.
+ ЗАПУСТИТИ ІМПОРТ
+
+ - %d книга успішно імпортована
+ - %d книги успішно імпортовані
+ - %d книг успішно імпортовано
+
+
+ - %d закладка успішно імпортована
+ - %d закладки успішно імпортовані
+ - %d закладок успішно імпортовано
+
+ Виберіть папку
+
+
+ Експорт метаданих
+
+ - Експорт бібліотеки (%d книга)
+ - Експорт бібліотеки (%d книги)
+ - Експорт бібліотеки (%d книг)
+
+ Експортувати тільки обране
+ Експортувати групи користувача
+
+ - Експорт черги (%d книга)
+ - Експорт черги (%d книги)
+ - Експорт черги (%d книг)
+
+
+ - Експорт закладок (%d елемент)
+ - Експорт закладок (%d елементи)
+ - Експорт закладок (%d елементів)
+
+ Експорт метаданих створить список ваших книг у форматі JSON.\nЦе не спосіб переміщення вашої колекції на компʼютер чи SD-карту.\nНатисніть тут, щоб дізнатися, як це зробити.
+ Книги експортуються без зображень.\nВам доведеться завантажити їх знову після відновлення, якщо папка зображень відсутня.
+ ЗАПУСТИТИ ЕКСПОРТ
+ Імпорт налаштувань
+ Успішно імпортовано
+
+
+ Ви маєте надати дозволи перед тим, як продовжити
+ Ви маєте вручну надати дозволи перед тим, як продовжити
+ Імпорт скасовано
+ Неможливо використувати цю папку — будь ласка, виберіть іншу
+ Ви не можете вибрати папку додатка як зовнішню — будь ласка, виберіть іншу
+ Ви не маєте вибирати папку з усіма завантаженнями цього пристрою — будь ласка, створіть чи виберіть іншу
+ Не вдалося створити папку Hentoid — будь ласка, виберіть інше розташування
+ Імпорт зупинено
+ Імпорт був несподівано зупинено
+ Завдання завершено — дивіться логи для подробиць
+ Помилка — контент не виявлено
+ Hentoid виявив існуючу бібліотеку.\nЧи хочете ви імпортувати її\?
+ Імпорт бібліотеки
+ Імпорт починається…
+ Імпорт бібліотеки
+ Імпорт завершено
+
+ - Успішно імпортовано: %s
+ - Успішно імпортовано: %s
+ - Успішно імпортовано: %s
+
+
+ - Не вдалося імпортувати: %s
+ - Не вдалося імпортувати: %s
+ - Не вдалося імпортувати: %s
+
+
+
+ Використання памʼяті
+ Всього: %s
+ Вільно: %s
+ Hentoid (основне сховище): %s
+ Hentoid (зовнішнє сховище): %s
+ База даних: %1$s (заповнено на %2$.1f%%)
+ Подробиці (основне сховище)
+ Джерело
+ Книги
+ Розмір
+
+
+ Останні логи
+ Відкрити лог
+ Читати лог
+ Поділитися логом
+ Немає помилок
+ Оновлення/імпорт зовнішнеї бібліотеки
+ Оновлення/імпорт основной бібліотеки
+ Очищення основної бібліотеки
+ Міграція бібліотеки з Hentoid 1.11
+
+
+ У вас немає прав на запис у цій папці, спробуйте іншу.
+ Помилка створення папки.
+ Помилка відкриття файлу: не знайдено відповідного додатка
+ Помилка відкриття лінку: браузер не виявлено
+
+
+ Спробувати завантажити знову\?
+ Всього зображень у завантаженні: %1$d\nЗображень без помилок: %2$d\nЗображень з помилками: %3$d
+ Перша помилка: %s
+ ЗАВАНТАЖИТИ ЗНОВУ
+ Відкрити лог помилок
+ Поділитися логами помилок
+ Генерую файл логов…
+
+
+ Відновити читання\?
+ Відкрити меню
+ Закрити меню
+
+ - Видалити %d вибраний елемент\?
+ - Видалити %d вибраних елементи\?
+ - Видалити %d вибраних елементів\?
+
+
+ - Заархівувати %d вибраний елемент\?
+ - Заархівувати %d вибраних елементи\?
+ - Заархівувати %d вибраних елементів\?
+
+ Назва нової групи\?
+ Редагувати назву групи
+ Редагувати назву книги
+ Видаляю книги
+ Знищаю книги
+ Скасовую елементи черги
+
+ - %d книга
+ - %d книги
+ - %d книг
+
+
+ - %d група
+ - %d групи
+ - %d груп
+
+ успішно видалено
+ Архівую книги
+ Готово: %s
+ Архівування завершено
+
+ - %d книга успішно заархівована
+ - %d книги успішно заархівовані
+ - %d книг успішно заархівовані
+
+ Архівація не вдалася
+ Не вдалося архівувати принаймні одну книгу
+
+ - %d книга заархівована в Завантаженнях
+ - %d книги заархівовані в Завантаженнях
+ - %d книг заархівовані в Завантаженнях
+
+ Потокову передачу скасовано — книга не доступна в мережі
+ Завантаження скасовано — книга не доступна в мережі
+ Натисніть \'Назад\' двічі, щоб вийти.
+ Чому в мені порожньо\?
+ Помилок при завантаженні не виявлено
+ Ну давай, скачай щось.
+ Завантажую~
+ Немає результатів :(
+ Тут такого немає, йди, скачай це.
+ Пошук \'%s\' в…
+ ID: \'%s\'
+
+ - %d елемент
+ - %d елементи
+ - %d елементів
+
+
+ - %1$d результат із %2$d
+ - %1$d результати з %2$d
+ - %1$d результатів із %2$d
+
+ Зміни будуть застосовані після перезапуску Hentoid
+
+ - Завантажити вибране знову\?
+ - Завантажити вибране знову\?
+ - Завантажити вибране знову\?
+
+
+ - Завантажити вибране\?
+ - Завантажити вибране\?
+ - Завантажити вибране\?
+
+
+ - Стріміти вибране\?
+ - Стріміти вибране\?
+ - Стріміти вибране\?
+
+
+ - %d книга знаходиться у зовнішній бібліотеці й не може бути завантажена знову\nЦі книги будуть пропущені.
+ - %d книги знаходяться у зовнішній бібліотеці й не можуть бути завантажени знову\nЦі книги будуть пропущені.
+ - %d книги знаходяться у зовнішній бібліотеці й не можуть бути завантажени знову\nЦі книги будуть пропущені.
+
+
+ - %d книга вже завантажена\nЦі книги будуть пропущені.
+ - %d книги вже завантажені\nЦі книги будуть пропущені.
+ - %d книг вже завантажено\nЦі книги будуть пропущені.
+
+
+ - %d книга вже стріміться або знаходиться у зовнішній бібліотеці\nЦі книги будуть пропущені.
+ - %d книги вже стрімляться або знаходяться у зовнішній бібліотеці\nЦі книги будуть пропущені.
+ - %d книг вже стрімляться або знаходяться у зовнішній бібліотеці\nЦі книги будуть пропущені.
+
+ Шукайте за чому завгодно: назвою, художнику, теґам\nі навіть ID книги!
+ Ви можете проводити пальцем за номерами сторінок
+
+ - Вибрано %d елемент
+ - Вибрано %d елементи
+ - Вибрано %d елементів
+
+ Помилка — Батьківська папка недоступна
+ Помилка — Неправильний URL
+ Помилка — Сайт не підтримується
+
+ - %d книга з зовнішньої бібліотеки не буде видалена
+ - %d книги з зовнішньої бібліотеки не будуть видалені
+ - %d книг із зовнішньої бібліотеки не будуть видалені
+
+ Встановити як обкладинку групи
+ Встановити обкладинку цієї книги як обкладинку групи\?
+ Перемістити до групи користувача
+ Існуюча група
+ Створити нову
+ Назва нової групи користувача
+ Відʼєднати від цієї групи
+ Група з такою назвою вже існує.
+ Заборонено перейменовувати цю групу!
+ Назва групи не може бути порожньою!
+ Не вибрана група!
+
+ - Видалити лише виділене
+ - Видалити лише виділене
+ - Видалити лише виділене
+
+
+ - Видалити %d книгу
+ - Видалити %d книги
+ - Видалити %d книг
+
+
+ - Видалити групи та книги
+ - Видалити групи та книги
+ - Видалити групи та книги
+
+ Нема чого видаляти — увімкніть опцію \"Дозволити видаляти зовнішній контент\"
+
+
+ Поєднати
+ Поєднаю книги…
+ Розділяю книгу…
+ Розділити
+ Нова назва
+ Видалити ці книги після поєднання
+ Натисніть і утримуйте для вибору\nКожна вибрана глава перетвориться на нову книгу
+ Поки не створено глав :(
+ Створити глави
+ Успішно поєднано
+ Успішно розділено
+
+
+ Всі книги
+ За художниками
+ За датою завантаження
+ Свої групи
+ [без художника]
+
+
+ За зростаючою
+ За спадною
+ Пересортувати
+ За назвами
+ За художниками
+ За сторінками
+ За датою початку завантаження
+ За датою завершення завантаження
+ За датою читання
+ За прочитанням
+ За розміром
+ За прогресом
+ Випадково
+ Своє
+ -невірно-
+ За кількістю книг
+ За датою початку завантаження
+ Фільтри
+ Відображення обраних книг
+ Відображати тільки завершене
+ Відображати не тільки завершене
+
+
+ Показати групи
+ Показати
+ Художники
+ Групи
+
+
+ Перемістити елемент
+ Знаходиться у зовнішній бібліотеці
+ Книга передається потоком
+ Переглянути джерело у браузері
+ Додати/видалити з обраного
+
+
+ У вас вже є ця книга. Шукати в інших джерелах\?
+
+
+ Оновлення
+ Перевірка оновлень
+ Доступно оновлення!
+ Натисніть для завантаження
+ Не вдалося завантажити оновлення
+ Натисніть, щоб повторити спробу
+ Завантажується оновлення
+ Оновлення готово!
+ Натисніть для інсталяції
+
+
+ Зверніть увагу, що завантаження можуть не працювати на цьому сайті.
+ Кнопка завантаження наразі не працює на цьому сайті.
+ Цей сайт зараз працює нестабільно. Додаток не може нічого з цим зробити. Будь ласка, спробуйте пізніше.
+ Цей сайт зараз недоступний. Додаток не може нічого з цим зробити. Будь ласка, спробуйте пізніше.
+ Команда розробників Hentoid працює над виправленням. Ми повідомимо вас, коли нова версія додатка буде доступна.
+ Виправлення доступне в останній версії програми. Будь ласка, інсталюйте оновлення.
+
+
+ Введіть код-пароль
+ Блокування увімкнено
+ Блокування вимкнено
+ Змінити код-пароль
+ Код-пароль змінено
+ Введіть поточний код-пароль
+ Введіть новий код-пароль
+ Підтвердіть новий код-пароль
+ Блокування кодом-паролем запобігає використанню цього додатка без попередньої авторизації за допомогою 4-значного PIN-коду
+ Блокувати при\nповторному відкритті
+
+
+ Hentoid — це додаток для завантаження додзінсі і хентай-манґи. Hentoid спочатку був створений Сесаром Арасакі.
+ ЗАЯВА ПРО КОНФІДЕНЦІЙНІСТЬ ПЕРСОНАЛЬНИХ ДАНИХ\n\nHentoid — це безкоштовне програмне забезпечення з відкритим кодом, розроблене спільнотою Hentoid (приєднуйтесь до нас у Discord за лінком нижче)
+ Hentoid збирає наступні персональні дані і ділиться ними з Google для обробки за допомогою бібліотеки Firebase:\n - Ідентифікатор сесії\n - IP-адреса
+ Вищезазначені дані видаляються як з діючої, так і з запасної систем Firebase протягом 180 днів (подробиці доступні на https://firebase.google.com/support/privacy/)\n\nНіякі інші персональні дані не збираються і не передаються третім особам.
+ Показати ліцензії
+ Версія Hentoid: %1$s (%2$d)
+ Версія Chrome: %1$d
+
+
+ Назад до останньої сторінки галереї
+ Назад до галереї
+ Назад
+ Уперед
+ Закладки
+ Оновити/зупинити
+ Скопіювати адресу
+ Завантажити
+ Цей контент не може бути завантажений. Соррі~
+ URL скопійовано в буфер обміну
+ Ви зайшли на сторінку книги. Саме час завантажити її за допомогою кнопки внизу!
+ Знайдено заблокований теґ (%s)\nЗавантаження скасовано
+ Знайдено заблокований теґ (%s)\nДодано в чергу як помилка
+ Додати в закладки
+ Видалити з закладок
+ Скопіювати адресу закладки
+ Зробити адресу закладки домашньою сторінкою
+ У цієї книги є наступні заблоковані теґи: %s
+ Домашня сторінка
+ Не вдалося почати стрімінг: %s
+ Відсутній дозвіл на запис — неможливо використовувати завантажувач
+
+ Будь ласка, створіть або використовуйте існуючий акаунт exHentai.
+ Неправильні дані, будь ласка, повторіть спробу входу
+ Авторизація не завершена, будь ласка, повторіть спробу входу. Якщо проблема зберігається, спробуйте відвідати сайт з іншої країни або створіть новий акаунт.
+
+
+ Будь-яке
+ Виключити
+ Шукаємо %s
+ Шукати %s
+
+ Художник (%1$d)
+ Теґ (%1$d)
+ Персонаж (%1$d)
+ Сеттинг (%1$d)
+ Мова (%1$d)
+ Джерело (%1$d)
+
+ художника
+ теґ
+ персонажа
+ сеттинг
+ мову
+ джерело
+ завантажувача
+ коло
+ категорію
+ перекладача
+ видавця
+
+ - Знайдено %s результат
+ - Знайдено %s результати
+ - Знайдено %s результатів
+
+ Виберіть фільтр
+
+
+ Неможливо завантажити книгу: Не знайдено жодного зображення
+ Завантаження: %1$d из %2$d
+ Завантаження зображення: %d%%
+ %1$d x %2$d (масштаб %3$.0f%%) - %4$s
+ Виберіть напрямок читання
+ Зліва направо
+ Справа наліво
+ Зверху вниз
+ Слайдшоу
+ Режим слайдшоу
+ Перемішати сторінки
+ Змінити порядок сторінок
+ Перемішування сторінок
+ Показувати лише обрані сторінки
+ Показувати всі сторінки
+ Показування обраних сторінок
+ Встановити як обкладинку
+ Видалити книгу
+ Скопійовано в Завантаження
+ Не вдалося скопіювати в Завантаження
+ Видалити цю сторінку\?
+ Інформація
+ Попередня книга
+ Наступна книга
+ Галерея книг
+ Додати/видалити сторінку з обраних
+ Скопіювати сторінку в Завантаження
+ Поділитися сторінкою зі стороннім додатком
+ Видалити сторінку
+
+ - Запитати мене знову наступного разу
+ - Більше не питати для цієї книги
+ - Більше не питати на час цієї сесії
+
+ Встановити цю сторінку як обкладинку книги\?
+ Зображення не знайдено
+ Стрімлю зображення…
+ Налаштування книги
+ Налаштування додатка
+ Використовувати налаштування додатка (%s)
+ Сторінка додана до обраного
+ Сторінка видалена з обраного
+ Книга додана до обраного
+ Книга видалена з обраного
+ Виправити помилку завантаження
+ Перезавантажити сторінку
+ Передодати книгу
+ Редагувати глави
+ Галерея
+ Глава
+ Щоб створити або видалити глави, натисніть на першу сторінку
+ Очистити всі глави
+ Додати та видалити глави
+ %1$sСторінка %2$d%3$s%4$s
+ Гл. %d
+ Немає глав
+ Перейменовую файли…
+ Не вдалося видалити контент
+ Не вдалося видалити сторінку
+ Не вдалося видалити файл
+ Відсутній дозвіл на запис — неможливо почати перегляд
+ Неможливо завантажити список книг
+ Не вдалося відкрити главу на сторінці %d
+ Не вдалося видалити глави
+ Не вдалося перемістити главу
+ Початок слайдшоу (затримка в %.1f с)
+ Слайдшоу зупинено
+ Видалити всі глави?
+
+
+ Що нового\?
+ Що нового\?*
+ Що нового\?
+ Доступна нова версія (%1$s)
+ Оновлення завершено!
+ Завантаження почато
+
+
+ Меню редагування
+
+
+ Пошук дублікатів
+ Назва
+ Обкладинка
+ Художник
+ Лише тей же мовою
+ Ігнорувати глави та сіквели
+ Чутливість
+
+ - Слабка
+ - Звичайна
+ - Сильна
+
+ Розпочати пошук
+ Індексую обкладинки
+ Визначаю дублікати
+
+ - %d дублікат
+ - %d дублікати
+ - %d дублікатів
+
+ Назва: %.0f%%
+ Назва: немає даних
+ Обкладинка: %.0f%%
+ Обкладинка: немає даних
+ Художник: %.0f%%
+ Художник: немає даних
+ Застосувати
+ Починаю визначення дублікатів
+ Визначення дублікатів завершено
+
+ - %d дублікат успішно визначено
+ - %d дублікати успішно визначено
+ - %d дублікатів успішно визначено
+
+ Нема чого показувати, почніть новий пошук
+ Йде визначення дублікатів
+
+ - %d дублікат
+ - %d дублікати
+ - %d дублікатів
+
+ Дублікатів не визначено
+ Залишити
+ Видалити
+
+
+ Увага: дублікат!
+ Книга, яку ви збираєтеся завантажити, може бути дублікатом книги, яка вже є в вас.
+ Книга, сторінки якої ви маєте намір завантажити, може бути не тією, яка є в вас.
+ Завжди завантажувати і більше не питати
+ Ніколи більше не додавати сторінки з потенційних дублікатів
+ Схожість: %.0f%%
+ Завантажити книгу
+ Завантажити додаткові сторінки
+ Зачекайте, будь ласка — йде індексування
+
+
+ Архівування контенту
+ Видалення контенту
+ Видалення завершено
+
+ - Видалено: %d
+ - Видалено: %d
+ - Видалено: %d
+
+ Видалення не вдалося
+ Не вдалося видалити принаймні одну книгу
+
+
+ Запуск
+ Запускається…
+ Запуск завершено
+
+
+ Сьогодні
+ На тижні
+ За цей місяць
+ За останні 60 днів
+ За останній рік
+ Давно
+ Без групи
+
+
+ Світла
+ Темно-червона
+ Темна
+
+
+ англійська
+ китайська
+ японська
+ корейська
+ іспанська
+ російська
+ португальска
+ тайська
+ французька
+ вʼєтнамська
+ італійська
+ німецька
+ польська
+ індонезійська
+ угорська
+ турецька
+ чеська
+
+
+ Виберіть ваш файловий менеджер
+ б.
+ КБ
+ МБ
+ ГБ
+ ТБ
+ ПБ
+ ЕБ
+
diff --git a/app/src/main/res/values-uk/strings_download_service.xml b/app/src/main/res/values-uk/strings_download_service.xml
new file mode 100644
index 0000000000..618f4a3a8b
--- /dev/null
+++ b/app/src/main/res/values-uk/strings_download_service.xml
@@ -0,0 +1,18 @@
+
+
+
+ - %d завантаження завершено
+ - %d завантаження завершено
+ - %d завантаження завершено
+
+ Завантаження завершено
+ Завантаження скасовано
+ Завантаження завершено, але з помилками
+
+ - %1$d елемент із %2$d додано до черги
+ - %1$d елементи з %2$d додано до черги
+ - %1$d елементів із %2$d додано до черги
+
+ Вже завантажено
+ Вже у черзі
+
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings_settings.xml b/app/src/main/res/values-uk/strings_settings.xml
new file mode 100644
index 0000000000..43506d3b2c
--- /dev/null
+++ b/app/src/main/res/values-uk/strings_settings.xml
@@ -0,0 +1,189 @@
+
+
+
+
+ Інтерфейс та навігація
+ Бібліотека
+ Керування джерелами
+ Вмикайте, вимикайте та реорганізовуйте джерела, що відображаються в лівому меню
+ Спосіб відображення
+ Вид відображення книг та груп\nПоточний: %s
+ Нескінченна прокрутка
+ Включає нескінченне прокручування у списку завантажень\nВимикає поділ на сторінки
+ Прокручуйте, скільки вашій душі завгодно
+ Кількість елементів на сторінці
+ Визначає, скільки елементів відображається на одній сторінці у списку завантажень\nЗараз: %s елементів
+ Кнопка переходу наверх
+ Вимкнена
+ Увімкнена
+ Розширений пошук
+ Спосіб сортування метаданих
+ Відображати кількість доступних книг
+ Кількість доступних книг відображається поруч із кожною характеристикою
+ Кількість доступних книг не відображається
+ Тема додатка
+ Схема кольорів
+ Поточна тема: %s
+
+
+ Сховище
+ Бібліотека Hentoid
+ Вибір папки завантажень
+ Виберіть, де Hentoid буде зберігати ваші завантаження
+ Додавати порожні книги в чергу як невдалі завантаження
+ Порожні книги будуть додані в чергу як помилки завантаження
+ Порожні книги будуть додані до бібліотеки
+ Назви папок
+ Спосіб найменування папок:\n%s
+ Імпортування вже запущено
+ Вимкнути зовнішню бібліотеку\?
+ Зовнішня бібліотека вимкнена
+ Скорочувати назви папок
+ Урізання назв папок\nЗараз: %s
+ Використання пам\'яті
+ Вільне місце: %.2f%% (натисніть для подробиць)
+ Попереджати при майже повному заповненні пам\'яті
+ Зовнішня бібліотека
+ Зовнішня бібліотека
+ Зовнішні бібліотеки не встановлені
+ Дозволяти видалення зовнішнього контенту
+ Hentoid може видаляти книги та сторінки у зовнішній бібліотеці
+ Hentoid не може видаляти книги та сторінки у зовнішній бібліотеці
+ Вимкнути зовнішню бібліотеку
+ Не використовувати зовнішню бібліотеку в Hentoid
+ Додатково
+ Оновити бібліотеку
+ Вручну оновити вміст бібліотеки
+ Видалити всі книги, крім обраних
+ Очистити бібліотеку і залишити лише обрані книги\nУвага: не відноситься до зовнішньої бібліотеки
+
+ - Видалити %d книгу\?
+ - Видалити %d книги\?
+ - Видалити %d книг\?
+
+
+
+ Браузер
+ Поведінка
+ Відновлювати останню сторінку
+ Сайти відкриватимуться зі своїх головних сторінок
+ Сайти відкриватимуться з останньої сторінки, яка була відкрита на них
+ Завантаження
+ Швидке завантаження
+ Не відкриваючи книгу, натисніть на неї, щоб завантажити
+ Заблоковані на книги nhentai відображаються…
+ Відображаються з розмиттям
+ Не відображаються
+ Поріг довгого натискання для швидкого завантаження
+ Не відпускайте палець протягом цього часу після натискання, щоб почати швидке завантаження\nЗараз: %s
+ Перевірка на дублікати
+ Ні: завжди завантажувати
+ Так: запитувати перед завантаженням
+ Намагатися знайти додаткові сторінки на потенційних дублікатах\?
+ Шукати додаткові сторінки лише на оригіналі
+ Шукати додаткові сторінки на потенційних дублікатах
+ Помічати завантажені книги
+ Вже завантажені книги не виділяються
+ Вже завантажені книги виділяються\nУвага: працює не на всіх сайтах
+ Зовнішній вигляд
+ Поліпшення якості життя
+ Використовується стандартна поведінка
+ Реклама видаляється, а обробка відбувається швидше
+ Вимкнути огляд сторінки
+ Внизу ви можете виставити бажаний рівень масштабу за замовчуванням
+ Скористайтеся налаштуванням внизу для визначення бажаного рівня масштабу за замовчуванням\nПримітка: Працює лише на hitomi та e-hentai
+ Масштаб за замовчуванням
+ Рівень масштабу під час завантаження сторінки\nЗараз: %s%%
+ Сторонній DNS
+ DNS через HTTPS
+
+
+ Завантажувач
+ Автоматично починати чергу завантаження
+ Черга завантаження розпочнеться як тільки завантаження завершиться
+ Черга завантаження запуститься лише вручну
+ Завантажувати тільки через Wi-Fi
+ Черга завантаження розпочнеться лише якщо є підключення до Wi-Fi
+ Черга завантаження розпочнеться незалежно від типу мережі
+ Розташування нових завантажень
+ Пропускати великі завантаження на мобільній мережі
+ Великі завантаження будуть пропущені, якщо відсутнє підключення до Wi-Fi
+ Усі завантаження будуть оброблені
+ Обмеження на розмір завантажень
+ Обмеження на кількість сторінок у завантаженнях
+ Автоматично намагатись повторно завантажувати
+ При невдалих спробах завантаження воно буде здійснено знову
+ Невдалі завантаження відбуватимуться повторно
+ Невдалі завантаження будуть позначені як помилки
+ Кількість повторних спроб
+ Обмеження використання пам\'яті
+ Блокування теґів
+ Заблоковані теґи
+ Книги з певними теґами не завантажуватимуться\nПідказка: теґи розділяються комами
+ Погані теґи має…
+ Кількість паралельних завантажень
+ Кількість зображень, що завантажуються одночасно\nЗараз: %s
+
+
+ Конфіденційність
+ Перегляд у списку нещодавніх додатків
+ У списку останніх додатків не відображається, що відкрито в додатку\nУвімкніть, якщо хочете робити скріншоти додатка
+ Вміст екрана не ховається у списку нещодавніх додатків
+ Дозволити скріншоти
+ Перегляд у списку недавніх додатків та скріншоти в додатку вимкнено
+ Скріншоти дозволені
+ Аналітика Firebase
+ Збір аналітики вимкнено.\nУвімкніть його, щоб ми могли покращувати додаток, відстежуючи збої та помилки в ньому
+ Збір аналітики увімкнено.\nДякуємо, що допомагаєте нам покращити Hentoid!
+
+
+ Оновлення
+ Перевірити оновлення вручну
+ Зробити перевірку прямо зараз
+ Перевіряю оновлення…
+ Оновлень не знайдено — Ви вже використовуєте останню версію
+ Не вдалося перевірити оновлення. Перевірте з\'єднання або спробуйте пізніше
+ Автоматичні оновлення
+ Ви також можете увімкнути автоматичну перевірку оновлень по мобільній мережі
+ Це не вимикає можливість ручної перевірки оновлень
+
+
+ Перегляд зображень
+ Управління
+ Режим огляду
+ Режим перегортання сторінок
+ Свайп
+ Натискання на край
+ Подвійне збільшення зони натискання
+ Кнопки гучності
+ Стрілки вліво/вправо на клавіатурі
+ Перегортати кілька сторінок одним свайпом
+ Інвертувати кнопки гучності
+ Збільшення
+ Тимчасово збільшувати при утриманні пальця
+ Обмежити зум за подвійним або довгим натисканням
+ Зовнішній вигляд
+ Не вимикати екран
+ Завжди показувати номер сторінки
+ Увімкнути анімацію зміни сторінок
+ Увімкнути анімацію зуму
+ Смуги, що розділяють у вертикальному режимі
+ Режим відображення для горизонтального перегляду
+ Режим відмальовування
+ Плавне відмальовування не доступно на Android 5
+ Автоматично повертати зображення
+ Зображення не повертаються
+ Зображення повертаються так, щоб зайняти якнайбільше місця на екрані
+ Число стовпців у галереї
+ Навігація та поведінка
+ Відновлювати читання при повторному відкритті книг
+ Книги завжди відкриватимуться з першої сторінки
+ Раніше відкриті книги відкриватимуться з останньої переглянутої сторінки, крім випадків, коли книга була дочитана
+ Завжди відкривати у режимі галереї
+ Безперервне читання
+ Остання сторінка книги справді остання
+ З останньої сторінки книги можна піти на наступну книгу
+ Вважати книгу прочитаною після N сторінок
+ Слайдшоу / Час показу кожної сторінки
+ Деякі сайти погано працюють з DNS через HTTPS. Скористайтеся стороннім додатком для DNS, якщо ви маєте проблеми при навігації (наприклад: порожні сторінки або помилки під час авторизації)
+
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings_slides.xml b/app/src/main/res/values-uk/strings_slides.xml
new file mode 100644
index 0000000000..e8ed05e0e8
--- /dev/null
+++ b/app/src/main/res/values-uk/strings_slides.xml
@@ -0,0 +1,22 @@
+
+
+ Ласкаво просимо до Hentoid!
+ Hentoid — це додаток для завантаження\nдодзінсі та хентай-манґи.
+ Дозволи
+ Нам потрібен ваш дозвіл, щоб зберігати файли на пристрої.
+ Бібліотека
+ Hentoid потрібна окрема папка для зберігання файлів.
+
+ Пропустити
+
+ Пропустити\?
+ Якщо ви не пропустите цей крок, ви не зможете нічого завантажити.\nДля завантаження вам потрібно вибрати папку завантажень у налаштуваннях додатка.
+ Тема
+ Виберіть тему.
+ Джерела
+
+ Виберіть джерела для завантажень.
+ Ви можете змінити цей вибір пізніше в будь-який момент, натиснувши на \'Змінити\' у меню зліва.
+ От і все!
+ Все готово!\nНасолоджуйтесь додатком.
+
diff --git a/app/src/main/res/values-uk/strings_tools.xml b/app/src/main/res/values-uk/strings_tools.xml
new file mode 100644
index 0000000000..2608c244af
--- /dev/null
+++ b/app/src/main/res/values-uk/strings_tools.xml
@@ -0,0 +1,47 @@
+
+
+
+ Інструменти
+
+ Імпорт та експорт
+
+
+ Експортувати метадані
+
+ Експортувати книги з Hentoid в JSON
+
+ Імпортувати метадані
+
+ Відновити книги в Hentoid з JSON
+
+ Налаштування
+
+ Експортувати налаштування
+
+ Експортувати налаштування в JSON
+
+ Імпортувати налаштування
+
+ Відновити налаштування з JSON
+
+
+ Пошук дублікатів
+
+ Пошук та видалення ідентичних копій книг
+
+
+ Керування кешем
+
+ Очистити кеш браузера
+
+ Кеш WebView успішно очищено
+
+ Очистити кеш додатка
+
+ Кеш додатка успішно очищено
+
+
+ Логи додатка
+
+ Переглянути останні логи додатка
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 14f6f420c0..0ccbd3df67 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -19,4 +19,5 @@
+
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index 28a23fb271..259a4908e6 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -355,6 +355,7 @@
0
true
20
+ true
1
%s
true
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 59cd78a770..a8e66ceb11 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -26,6 +26,7 @@
- file
- files
+ Groups
Pause
Stop
Resume
@@ -36,6 +37,7 @@
Open folder
default
empty
+ None
API 29 migration
@@ -64,11 +66,11 @@
Search
Toggle favourite filter
- Toggle completed filter
Toggle favourite
New group
- Edit order
- Edit name
+ Reorder
+ Rename
+ Confirm editing
Cancel editing
New custom ordering will overwrite the previous one.
Order books
@@ -91,7 +93,7 @@
App settings
Book settings
File permissions have been lost
- Your device is low on memory!
+ Your device is low on storage!
Fix
View queue
@@ -294,8 +296,8 @@
- %s failed
-
- Memory usage
+
+ Storage usage
Total: %s
Free: %s
Hentoid (primary): %s
@@ -336,9 +338,6 @@
Resume reading from where you left\?
Open drawer
Close drawer
- How would you like to filter completed books\?
- Show completed only
- Show not completed only
- Delete the selected item\?
- Delete %d selected items\?
@@ -470,18 +469,22 @@
Content merged
Content split
-
+
All books
By Artist
- By Download date
- Custom
+ By Download date (processed)
+ Custom groups
[no artist]
-
+
+ Ascending
+ Descending
+ Reshuffle
Title
Artist
Pages
- Download date
+ Download date (processed)
+ Download date (completed)
Read date
Reads
Size
@@ -490,6 +493,17 @@
Custom
-invalid-
Books
+ Upload date
+ Filters
+ Toggle display of favourite books
+ Show completed books only
+ Show not completed books only
+
+
+ Display groups
+ Display
+ Artists
+ Groups
Drag item
@@ -499,9 +513,6 @@
Mark/unmark as favourite
- Show artists
- Show groups
- Show artists & groups
You already have this book. Search other sources\?
@@ -620,7 +631,7 @@
Toggle page shuffling
Only show favourite pages
Show all pages
- Toggle display of favourite page
+ Toggle display of favourite pages
Set as cover
Delete book
Copied to Downloads folder
diff --git a/app/src/main/res/values/strings_settings.xml b/app/src/main/res/values/strings_settings.xml
index 71e67b735a..62c036e7fe 100644
--- a/app/src/main/res/values/strings_settings.xml
+++ b/app/src/main/res/values/strings_settings.xml
@@ -12,6 +12,9 @@
Scroll away to your heart\'s content
Quantity Per Page
Number of items per page in download list\nCurrently: %s items
+ Go to Top button
+ Button disabled
+ Button enabled
Advanced search
Sorting order of displayed metadata
Count available books
@@ -36,9 +39,9 @@
External library detached
Truncate folder name
Book folder name truncation\nCurrently: %s
- Memory usage
+ Storage usage
Current free space: %.2f%% (tap for details)
- Alert on low memory
+ Alert on low storage
External library
External library
No external library is set
diff --git a/app/src/main/res/values/strings_slides.xml b/app/src/main/res/values/strings_slides.xml
index a78711c04d..aa0970d9f0 100644
--- a/app/src/main/res/values/strings_slides.xml
+++ b/app/src/main/res/values/strings_slides.xml
@@ -1,12 +1,9 @@
-
Welcome to Hentoid!
Hentoid is a Doujinshi\n& H-Manga archiving app.
-
Permissions
We need to ask for your permission to store files on your device.
-
Library
Hentoid requires a dedicated folder for storing your media.
@@ -14,15 +11,12 @@
Skip\?
You won\'t be able to download if you skip this step.\nIn order to download, you\'ll have to set the download folder through the app settings.
-
Theme
Choose a display theme.
-
Sources
Select sources to download from.
You can change this selection later at any time using the edit button on the left menu.
-
All set up!
We are set!\nEnjoy the app.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 904c4d9605..274d956684 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,4 +1,4 @@
-
+
+
+
+
+