Add Option to Select Downloads Grouping Mode (#998)
* Add Option to Select Downloads Grouping Mode * hide Drag and Edit Button when Mode is Artist * Revert "hide Drag and Edit Button when Mode is Artist" some changes are not filtered when pushed This reverts commit 319812e156f61c32250d908ed5ed22720ea0c6b7. * hide Drag and Edit Button when Mode is Artist * declared is typo * use ComicInfo.penciller * remove BaseGalleryInfo.artist * Correct invalidate key * reImport kotlin.math.roundToInt, it was incorrectly deleted * use movableContentOf * optimize imports * use remember not rememberInVM * Get DownloadInfo.artists from Database or `ComicInfo.xml` * refactor * unknown artist group * redundant parameter * reformat code * SearchBar title and hint * Translation * Fix when initially loaded, the count of DownloadLabel is zero. * lazy invalid * use remember not rememberInVM * refactor * Fix LazyColumn not updated * refactor * Hide some Button in Artist Mode * Revert * Fix update labelList not updated UI * Revert reFormat * use trailing lambda * Revert rememberUpdatedState * Translate * use buildMap * Move ARTIST to DOWNLOAD_ARTIST * Revert multi-line for chained Modifier * Generate DownloadInfo.artistInfoList after writeComicInfo and restoreDownload * Fixed crash after download gallery * Use a composite key for DOWNLOAD_ARTISTS * Make artists non-null * Refactor * Only update artist info when writing comic info * Separate list for pending artist info * Remove unused * Put download artist when import database * Use a composite key of GID and ARTIST * Refactor * Cache labelList * named lambda parameters * Refactor to avoid `mutex` * follow up on `Make artists non-null` * ArtistList order by ARTIST * Lift EhTagDatabase check * Translate artist in download title * Refactor * Refactor readMetadataFromLocal Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> * Do not update the DB when the `artists` is empty * Remove unused --------- Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
16 files changed
+841
-65
No files matched your search
@@ -0,0 +1,564 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 23,
|
||||
"identityHash": "7d1cfc4e9a9d506a73eb0520ae414d25",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "GALLERIES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT NOT NULL, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_TAGS` TEXT, `PAGES` INTEGER NOT NULL DEFAULT 0, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "TOKEN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbKey",
|
||||
"columnName": "THUMB",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "posted",
|
||||
"columnName": "POSTED",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uploader",
|
||||
"columnName": "UPLOADER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "RATING",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleTags",
|
||||
"columnName": "SIMPLE_TAGS",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "pages",
|
||||
"columnName": "PAGES",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleLanguage",
|
||||
"columnName": "SIMPLE_LANGUAGE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "favoriteSlot",
|
||||
"columnName": "FAVORITE_SLOT",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_LABELS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"LABEL"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOADS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "state",
|
||||
"columnName": "STATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "legacy",
|
||||
"columnName": "LEGACY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "label",
|
||||
"columnName": "LABEL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_DOWNLOADS_LABEL",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"LABEL"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "DOWNLOAD_LABELS",
|
||||
"onDelete": "SET NULL",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"LABEL"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"LABEL"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_DIRNAME",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dirname",
|
||||
"columnName": "DIRNAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "DOWNLOAD_ARTISTS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `ARTIST` TEXT NOT NULL, PRIMARY KEY(`GID`, `ARTIST`), FOREIGN KEY(`GID`) REFERENCES `DOWNLOADS`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "ARTIST",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID",
|
||||
"ARTIST"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "DOWNLOADS",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "FILTER",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "TEXT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "enable",
|
||||
"columnName": "ENABLE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "HISTORY",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "LOCAL_FAVORITES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "TIME",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "NO ACTION",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "PROGRESS",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "gid",
|
||||
"columnName": "GID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "page",
|
||||
"columnName": "PAGE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"GID"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "GALLERIES",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"GID"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"GID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "QUICK_SEARCH",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "MODE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "CATEGORY",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyword",
|
||||
"columnName": "KEYWORD",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "advanceSearch",
|
||||
"columnName": "ADVANCE_SEARCH",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minRating",
|
||||
"columnName": "MIN_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageFrom",
|
||||
"columnName": "PAGE_FROM",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "pageTo",
|
||||
"columnName": "PAGE_TO",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "POSITION",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"ftsVersion": "FTS4",
|
||||
"ftsOptions": {
|
||||
"tokenizer": "simple",
|
||||
"tokenizerArgs": [],
|
||||
"contentTable": "GALLERIES",
|
||||
"languageIdColumnName": "",
|
||||
"matchInfo": "FTS4",
|
||||
"notIndexedColumns": [],
|
||||
"prefixSizes": [],
|
||||
"preferredOrder": "ASC"
|
||||
},
|
||||
"contentSyncTriggers": [
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_BEFORE_UPDATE BEFORE UPDATE ON `GALLERIES` BEGIN DELETE FROM `GALLERIES_FTS` WHERE `docid`=OLD.`rowid`; END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_BEFORE_DELETE BEFORE DELETE ON `GALLERIES` BEGIN DELETE FROM `GALLERIES_FTS` WHERE `docid`=OLD.`rowid`; END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_AFTER_UPDATE AFTER UPDATE ON `GALLERIES` BEGIN INSERT INTO `GALLERIES_FTS`(`docid`, `TITLE`, `TITLE_JPN`, `SIMPLE_TAGS`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`, NEW.`SIMPLE_TAGS`); END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_AFTER_INSERT AFTER INSERT ON `GALLERIES` BEGIN INSERT INTO `GALLERIES_FTS`(`docid`, `TITLE`, `TITLE_JPN`, `SIMPLE_TAGS`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`, NEW.`SIMPLE_TAGS`); END"
|
||||
],
|
||||
"tableName": "GALLERIES_FTS",
|
||||
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`TITLE` TEXT NOT NULL, `TITLE_JPN` TEXT NOT NULL, `SIMPLE_TAGS` TEXT, content=`GALLERIES`)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "titleJpn",
|
||||
"columnName": "TITLE_JPN",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "simpleTags",
|
||||
"columnName": "SIMPLE_TAGS",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": []
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7d1cfc4e9a9d506a73eb0520ae414d25')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import android.net.Uri
|
||||
import arrow.fx.coroutines.release
|
||||
import arrow.fx.coroutines.resource
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadArtist
|
||||
import com.hippo.ehviewer.dao.DownloadDirname
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.DownloadLabel
|
||||
@@ -113,8 +114,11 @@ object EhDB {
|
||||
dao.insertOrIgnore(downloadDirnameList)
|
||||
}
|
||||
|
||||
val downloadsCount
|
||||
get() = db.downloadsDao().count()
|
||||
val downloadsCountByLabel
|
||||
get() = db.downloadsDao().countByLabel()
|
||||
|
||||
val downloadsCountByArtist
|
||||
get() = db.downloadsDao().countByArtist()
|
||||
|
||||
suspend fun getAllDownloadLabelList() = db.downloadLabelDao().list()
|
||||
|
||||
@@ -142,6 +146,14 @@ object EhDB {
|
||||
dao.fill(raw.position)
|
||||
}
|
||||
|
||||
suspend fun putDownloadArtist(gid: Long, artists: List<DownloadArtist>) {
|
||||
if (artists.isNotEmpty()) {
|
||||
val dao = db.downloadArtistDao()
|
||||
dao.deleteByGid(gid)
|
||||
dao.insert(artists)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removeLocalFavorites(galleryInfo: BaseGalleryInfo) {
|
||||
db.localFavoritesDao().deleteByKey(galleryInfo.gid)
|
||||
deleteGalleryInfo(galleryInfo)
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.runtime.remember
|
||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
import com.hippo.ehviewer.download.SortMode
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
@@ -118,6 +119,7 @@ object Settings : DataStorePreferences(null) {
|
||||
val security = boolPref("require_unlock", false)
|
||||
val animateItems = boolPref("animate_items", true)
|
||||
val recentDownloadLabel = stringOrNullPref("recent_download_label", null)
|
||||
val downloadFilterMode = intPref("download_filter_mode", DownloadsFilterMode.Default.flag)
|
||||
|
||||
var downloadScheme by stringOrNullPref("image_scheme", null)
|
||||
var downloadAuthority by stringOrNullPref("image_authority", null)
|
||||
|
||||
@@ -8,11 +8,11 @@ import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
BaseGalleryInfo::class, DownloadLabel::class, DownloadEntity::class, DownloadDirname::class,
|
||||
BaseGalleryInfo::class, DownloadLabel::class, DownloadEntity::class, DownloadDirname::class, DownloadArtist::class,
|
||||
Filter::class, HistoryInfo::class, LocalFavoriteInfo::class, ProgressInfo::class, QuickSearch::class,
|
||||
GalleryEntityFts::class,
|
||||
],
|
||||
version = 22,
|
||||
version = 23,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 4, to = 5, spec = Schema4to5::class),
|
||||
@@ -32,11 +32,13 @@ import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
AutoMigration(from = 19, to = 20),
|
||||
AutoMigration(from = 20, to = 21, spec = Schema20to21::class),
|
||||
AutoMigration(from = 21, to = 22, spec = Schema21to22::class),
|
||||
AutoMigration(from = 22, to = 23),
|
||||
],
|
||||
)
|
||||
@TypeConverters(FilterModeConverter::class)
|
||||
abstract class EhDatabase : RoomDatabase() {
|
||||
abstract fun galleryDao(): GalleryDao
|
||||
abstract fun downloadArtistDao(): DownloadArtistDao
|
||||
abstract fun downloadDirnameDao(): DownloadDirnameDao
|
||||
abstract fun downloadLabelDao(): DownloadLabelDao
|
||||
abstract fun downloadsDao(): DownloadsDao
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.hippo.ehviewer.dao
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
|
||||
@Entity(
|
||||
tableName = "DOWNLOAD_ARTISTS",
|
||||
primaryKeys = ["GID", "ARTIST"],
|
||||
foreignKeys = [
|
||||
ForeignKey(DownloadEntity::class, ["GID"], ["GID"], onDelete = ForeignKey.CASCADE),
|
||||
],
|
||||
)
|
||||
data class DownloadArtist(
|
||||
@ColumnInfo(name = "GID")
|
||||
val gid: Long,
|
||||
|
||||
@ColumnInfo(name = "ARTIST")
|
||||
val artist: String,
|
||||
) {
|
||||
companion object {
|
||||
fun from(gid: Long, artists: Iterable<String>) = artists.map { DownloadArtist(gid, it) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hippo.ehviewer.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface DownloadArtistDao {
|
||||
@Insert
|
||||
suspend fun insert(downloadArtists: List<DownloadArtist>)
|
||||
|
||||
@Query("DELETE FROM DOWNLOAD_ARTISTS WHERE GID = :gid")
|
||||
suspend fun deleteByGid(gid: Long)
|
||||
}
|
||||
@@ -67,9 +67,13 @@ data class DownloadInfo(
|
||||
@ColumnInfo(name = "DIRNAME")
|
||||
val dirname: String?,
|
||||
|
||||
@Relation(parentColumn = "GID", entityColumn = "GID")
|
||||
var artistInfoList: List<DownloadArtist> = emptyList(),
|
||||
|
||||
@Embedded
|
||||
val downloadInfo: DownloadEntity = DownloadEntity(galleryInfo.gid),
|
||||
) : GalleryInfo by galleryInfo, AbstractDownloadInfo by downloadInfo {
|
||||
|
||||
companion object {
|
||||
const val STATE_INVALID = -1
|
||||
const val STATE_NONE = 0
|
||||
@@ -79,3 +83,6 @@ data class DownloadInfo(
|
||||
const val STATE_FAILED = 4
|
||||
}
|
||||
}
|
||||
|
||||
val DownloadInfo.artists: List<String>
|
||||
get() = artistInfoList.map { it.artist }
|
||||
@@ -14,7 +14,11 @@ import kotlinx.coroutines.flow.Flow
|
||||
interface DownloadsDao {
|
||||
@Suppress("ktlint:standard:annotation")
|
||||
@Query("SELECT LABEL, COUNT(*) AS COUNT FROM DOWNLOADS LEFT JOIN DOWNLOAD_LABELS USING(LABEL) GROUP BY LABEL")
|
||||
fun count(): Flow<Map<@MapColumn("LABEL") String?, @MapColumn("COUNT") Int>>
|
||||
fun countByLabel(): Flow<Map<@MapColumn("LABEL") String?, @MapColumn("COUNT") Int>>
|
||||
|
||||
@Suppress("ktlint:standard:annotation")
|
||||
@Query("SELECT ARTIST, COUNT(*) AS COUNT FROM DOWNLOADS LEFT JOIN DOWNLOAD_ARTISTS USING(GID) GROUP BY ARTIST ORDER BY ARTIST")
|
||||
fun countByArtist(): Flow<Map<@MapColumn("ARTIST") String?, @MapColumn("COUNT") Int>>
|
||||
|
||||
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME")
|
||||
suspend fun list(): List<DownloadEntity>
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadArtist
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.DownloadLabel
|
||||
import com.hippo.ehviewer.image.Image
|
||||
@@ -174,7 +175,6 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
info = DownloadInfo(galleryInfo, galleryInfo.putToDownloadDir())
|
||||
info.label = label
|
||||
info.state = DownloadInfo.STATE_WAIT
|
||||
|
||||
// Add to all download list and map
|
||||
allInfoList.insertWith(info, comparator())
|
||||
mAllInfoMap[galleryInfo.gid] = info
|
||||
@@ -266,6 +266,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
|
||||
// Save to
|
||||
EhDB.putDownloadInfo(info)
|
||||
EhDB.putDownloadArtist(info.gid, info.artistInfoList)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,26 +531,63 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
|
||||
suspend fun readMetadataFromLocal() {
|
||||
val list = allInfoList.filter { it.pages == 0 || it.simpleTags == null }.parMapNotNull(concurrency = 5) { info ->
|
||||
val list = allInfoList.mapNotNull {
|
||||
val updateGallery = it.pages == 0 || it.simpleTags == null
|
||||
val updateArtist = it.artistInfoList.isEmpty()
|
||||
if (updateGallery || updateArtist) {
|
||||
Triple(it, updateGallery, updateArtist)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.parMapNotNull(concurrency = 5) { (info, updateGallery, updateArtist) ->
|
||||
info.downloadDir?.run {
|
||||
val comicInfo = findFile(COMIC_INFO_FILE)?.let { readComicInfo(it) }
|
||||
if (comicInfo != null) {
|
||||
info.pages = comicInfo.pageCount
|
||||
info.simpleTags = comicInfo.toSimpleTags()
|
||||
info.galleryInfo
|
||||
val galleryInfo = if (updateGallery) {
|
||||
info.pages = comicInfo.pageCount
|
||||
info.simpleTags = comicInfo.toSimpleTags()
|
||||
info.galleryInfo
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val artistList = if (updateArtist && comicInfo.penciller != null) {
|
||||
info.artistInfoList = DownloadArtist.from(info.gid, comicInfo.penciller)
|
||||
info.gid to info.artistInfoList
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (galleryInfo != null || artistList != null) {
|
||||
galleryInfo to artistList
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else if (info.pages == 0) {
|
||||
findFile(SPIDER_INFO_FILENAME)?.let {
|
||||
val galleryInfo = findFile(SPIDER_INFO_FILENAME)?.let {
|
||||
readCompatFromUniFile(it)?.run {
|
||||
info.pages = pages
|
||||
info.galleryInfo
|
||||
}
|
||||
}
|
||||
|
||||
galleryInfo?.let { it to null }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
EhDB.updateGalleryInfo(list)
|
||||
|
||||
val galleryInfoList = mutableListOf<BaseGalleryInfo>()
|
||||
list.forEach { (info, artists) ->
|
||||
info?.let {
|
||||
galleryInfoList.add(it)
|
||||
}
|
||||
artists?.let { (gid, updateList) ->
|
||||
EhDB.putDownloadArtist(gid, updateList)
|
||||
}
|
||||
}
|
||||
if (galleryInfoList.isNotEmpty()) EhDB.updateGalleryInfo(galleryInfoList)
|
||||
}
|
||||
|
||||
val isIdle: Boolean
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hippo.ehviewer.download
|
||||
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.artists
|
||||
|
||||
enum class DownloadsFilterMode(
|
||||
val flag: Int,
|
||||
val take: (info: DownloadInfo, label: String?) -> Boolean,
|
||||
) {
|
||||
CUSTOM(flag = 0, take = { info, label ->
|
||||
label == "" || info.label == label
|
||||
}),
|
||||
ARTIST(flag = 1, take = { info, label ->
|
||||
if (null == label) {
|
||||
info.artistInfoList.isEmpty()
|
||||
} else {
|
||||
label == "" || info.artists.contains(label)
|
||||
}
|
||||
}),
|
||||
;
|
||||
|
||||
companion object {
|
||||
val Default = CUSTOM
|
||||
fun from(flag: Int) = DownloadsFilterMode.entries[flag]
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@ import com.hippo.ehviewer.client.executeSafely
|
||||
import com.hippo.ehviewer.client.getImageKey
|
||||
import com.hippo.ehviewer.coil.read
|
||||
import com.hippo.ehviewer.coil.suspendEdit
|
||||
import com.hippo.ehviewer.dao.DownloadArtist
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
import com.hippo.ehviewer.download.tempDownloadDir
|
||||
import com.hippo.ehviewer.image.UniFileSource
|
||||
@@ -325,7 +327,13 @@ class SpiderDen(val info: GalleryInfo) {
|
||||
EhEngine.fillGalleryListByApi(listOf(info))
|
||||
}
|
||||
}
|
||||
info.getComicInfo().write(it)
|
||||
info.getComicInfo().apply {
|
||||
write(it)
|
||||
DownloadManager.getDownloadInfo(gid)?.let { downloadInfo ->
|
||||
downloadInfo.artistInfoList = DownloadArtist.from(gid, penciller.orEmpty())
|
||||
EhDB.putDownloadArtist(gid, downloadInfo.artistInfoList)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
logcat(it)
|
||||
}
|
||||
|
||||
@@ -2,18 +2,20 @@ package com.hippo.ehviewer.ui.screen
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
import com.hippo.ehviewer.util.containsIgnoreCase
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class DownloadsFilterState(
|
||||
val mode: DownloadsFilterMode,
|
||||
val label: String?,
|
||||
val state: Int = -1,
|
||||
val keyword: String = "",
|
||||
) : Parcelable
|
||||
|
||||
fun DownloadsFilterState.take(info: DownloadInfo) =
|
||||
(label == "" || info.label == label) && (state == -1 || info.state == state) &&
|
||||
mode.take(info, label) && (state == -1 || info.state == state) &&
|
||||
with(info) {
|
||||
title.containsIgnoreCase(keyword) || titleJpn.containsIgnoreCase(keyword) ||
|
||||
simpleTags?.any { it.containsIgnoreCase(keyword) } ?: false
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.compose.material.icons.automirrored.filled.Sort
|
||||
import androidx.compose.material.icons.automirrored.filled.ViewList
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.DoneAll
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.FilterList
|
||||
import androidx.compose.material.icons.filled.GridView
|
||||
@@ -50,6 +51,7 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
@@ -80,11 +82,14 @@ import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadManager.labelList
|
||||
import com.hippo.ehviewer.download.DownloadManager.downloadInfoList
|
||||
import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
import com.hippo.ehviewer.download.SortMode
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.big.Download
|
||||
@@ -110,6 +115,7 @@ import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
import com.hippo.ehviewer.ui.tools.thenIf
|
||||
import com.hippo.ehviewer.util.findActivity
|
||||
import com.hippo.ehviewer.util.mapToLongArray
|
||||
import com.jamal.composeprefs3.ui.ifTrueThen
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
@@ -129,7 +135,8 @@ import sh.calvin.reorderable.rememberReorderableLazyColumnState
|
||||
fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
var gridView by Settings.gridView.asMutableState()
|
||||
var sortMode by Settings.downloadSortMode.asMutableState()
|
||||
var filterState by rememberSaveable { mutableStateOf(DownloadsFilterState(Settings.recentDownloadLabel.value)) }
|
||||
val filterMode by Settings.downloadFilterMode.collectAsState { DownloadsFilterMode.from(it) }
|
||||
var filterState by rememberSaveable { mutableStateOf(DownloadsFilterState(filterMode, Settings.recentDownloadLabel.value)) }
|
||||
var invalidateKey by rememberSaveable { mutableStateOf(false) }
|
||||
var searchBarOffsetY by remember { mutableIntStateOf(0) }
|
||||
val animateItems by Settings.animateItems.collectAsState()
|
||||
@@ -144,18 +151,32 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
val activity = remember { findActivity<MainActivity>() }
|
||||
val density = LocalDensity.current
|
||||
val view = LocalView.current
|
||||
val canTranslate = Settings.showTagTranslations && EhTagDatabase.isTranslatable(context) && EhTagDatabase.initialized
|
||||
val ehTags = EhTagDatabase.takeIf { canTranslate }
|
||||
fun String.translateArtist() = ehTags?.getTranslation(TagNamespace.Artist.toPrefix(), this) ?: this
|
||||
val positionalThreshold = SwipeToDismissBoxDefaults.positionalThreshold
|
||||
val allName = stringResource(R.string.download_all)
|
||||
val defaultName = stringResource(R.string.default_download_label_name)
|
||||
val unknownName = stringResource(R.string.unknown_artists)
|
||||
val emptyLabelName = when (filterMode) {
|
||||
DownloadsFilterMode.ARTIST -> unknownName
|
||||
DownloadsFilterMode.CUSTOM -> defaultName
|
||||
}
|
||||
val title = stringResource(
|
||||
R.string.scene_download_title,
|
||||
with(filterState) { if (label == "") allName else label ?: defaultName },
|
||||
with(filterState) {
|
||||
when (label) {
|
||||
"" -> allName
|
||||
null -> emptyLabelName
|
||||
else -> if (mode == DownloadsFilterMode.ARTIST) label.translateArtist() else label
|
||||
}
|
||||
},
|
||||
)
|
||||
val hint = stringResource(R.string.search_bar_hint, title)
|
||||
val sortModes = stringArrayResource(id = R.array.download_sort_modes)
|
||||
val downloadStates = stringArrayResource(id = R.array.download_state)
|
||||
val list = remember(filterState, invalidateKey) {
|
||||
DownloadManager.downloadInfoList.filterTo(mutableStateListOf()) { info ->
|
||||
downloadInfoList.filterTo(mutableStateListOf()) { info ->
|
||||
filterState.take(info)
|
||||
}
|
||||
}
|
||||
@@ -166,8 +187,25 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
val labelEmpty = stringResource(R.string.label_text_is_empty)
|
||||
val defaultInvalid = stringResource(R.string.label_text_is_invalid)
|
||||
val labelExists = stringResource(R.string.label_text_exist)
|
||||
val downloadsCount by rememberInVM { EhDB.downloadsCount }.collectAsState(emptyMap())
|
||||
val totalCount = remember(downloadsCount) { downloadsCount.values.sum() }
|
||||
val downloadsCountGroupByArtist by rememberInVM { EhDB.downloadsCountByArtist }.collectAsState(emptyMap())
|
||||
val downloadsCountGroupByLabel by rememberInVM { EhDB.downloadsCountByLabel }.collectAsState(emptyMap())
|
||||
val downloadsCount = when (filterMode) {
|
||||
DownloadsFilterMode.CUSTOM -> downloadsCountGroupByLabel
|
||||
DownloadsFilterMode.ARTIST -> downloadsCountGroupByArtist
|
||||
}
|
||||
val artistList = remember(downloadsCountGroupByArtist) {
|
||||
downloadsCountGroupByArtist.keys.mapNotNull { artist -> artist?.let { it to it } }
|
||||
}
|
||||
val labelList by remember {
|
||||
derivedStateOf {
|
||||
DownloadManager.labelList.map { it.id!! to it.label }
|
||||
}
|
||||
}
|
||||
val groupList = when (filterMode) {
|
||||
DownloadsFilterMode.CUSTOM -> labelList
|
||||
DownloadsFilterMode.ARTIST -> artistList
|
||||
}
|
||||
val totalCount = remember(downloadsCountGroupByLabel) { downloadsCountGroupByLabel.values.sum() }
|
||||
|
||||
fun switchLabel(label: String?) {
|
||||
Settings.recentDownloadLabel.value = label
|
||||
@@ -185,46 +223,67 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
title = { Text(text = labelsStr) },
|
||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
launch {
|
||||
val text = awaitInputText(title = newLabel, hint = labelsStr) { text ->
|
||||
when {
|
||||
text.isBlank() -> labelEmpty
|
||||
text == defaultName -> defaultInvalid
|
||||
DownloadManager.containLabel(text) -> labelExists
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
DownloadManager.addLabel(text)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.NewLabel, contentDescription = null)
|
||||
}
|
||||
val letMeSelect = stringResource(R.string.let_me_select)
|
||||
IconButton(
|
||||
onClick = {
|
||||
launch {
|
||||
val selected = if (!Settings.hasDefaultDownloadLabel) {
|
||||
0
|
||||
} else {
|
||||
labelList.indexOfFirst { it.label == Settings.defaultDownloadLabel } + 2
|
||||
}
|
||||
showSelectActions(R.string.default_download_label, selected) {
|
||||
onSelect(letMeSelect) {
|
||||
Settings.hasDefaultDownloadLabel = false
|
||||
}
|
||||
onSelect(defaultName) {
|
||||
Settings.hasDefaultDownloadLabel = true
|
||||
Settings.defaultDownloadLabel = null
|
||||
}
|
||||
labelList.forEach { (label) ->
|
||||
onSelect(label) {
|
||||
Settings.hasDefaultDownloadLabel = true
|
||||
Settings.defaultDownloadLabel = label
|
||||
if (DownloadsFilterMode.CUSTOM == filterMode) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
launch {
|
||||
val text = awaitInputText(title = newLabel, hint = labelsStr) { text ->
|
||||
when {
|
||||
text.isBlank() -> labelEmpty
|
||||
text == defaultName -> defaultInvalid
|
||||
DownloadManager.containLabel(text) -> labelExists
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
DownloadManager.addLabel(text)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.NewLabel, contentDescription = null)
|
||||
}
|
||||
val letMeSelect = stringResource(R.string.let_me_select)
|
||||
IconButton(
|
||||
onClick = {
|
||||
launch {
|
||||
val selected = if (!Settings.hasDefaultDownloadLabel) {
|
||||
0
|
||||
} else {
|
||||
DownloadManager.labelList.indexOfFirst { it.label == Settings.defaultDownloadLabel } + 2
|
||||
}
|
||||
showSelectActions(R.string.default_download_label, selected) {
|
||||
onSelect(letMeSelect) {
|
||||
Settings.hasDefaultDownloadLabel = false
|
||||
}
|
||||
onSelect(defaultName) {
|
||||
Settings.hasDefaultDownloadLabel = true
|
||||
Settings.defaultDownloadLabel = null
|
||||
}
|
||||
DownloadManager.labelList.forEach { (label) ->
|
||||
onSelect(label) {
|
||||
Settings.hasDefaultDownloadLabel = true
|
||||
Settings.defaultDownloadLabel = label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Download, contentDescription = null)
|
||||
}
|
||||
}
|
||||
val custom = stringResource(R.string.select_grouping_mode_custom)
|
||||
val artist = stringResource(R.string.select_grouping_mode_artist)
|
||||
IconButton(
|
||||
onClick = {
|
||||
launch {
|
||||
showSelectActions(R.string.select_grouping_mode) {
|
||||
val select = { mode: DownloadsFilterMode ->
|
||||
filterState = filterState.copy(mode = mode, label = "")
|
||||
Settings.downloadFilterMode.value = mode.flag
|
||||
Settings.recentDownloadLabel.value = ""
|
||||
}
|
||||
onSelect(custom) { select(DownloadsFilterMode.CUSTOM) }
|
||||
onSelect(artist) { select(DownloadsFilterMode.ARTIST) }
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -235,10 +294,11 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
)
|
||||
|
||||
val labelsListState = rememberLazyListState()
|
||||
val editEnable = DownloadsFilterMode.CUSTOM == filterMode
|
||||
val reorderableLabelState = rememberReorderableLazyColumnState(labelsListState) { from, to ->
|
||||
val fromPosition = from.index - 2
|
||||
val toPosition = to.index - 2
|
||||
labelList.apply { add(toPosition, removeAt(fromPosition)) }
|
||||
DownloadManager.labelList.apply { add(toPosition, removeAt(fromPosition)) }
|
||||
view.performHapticFeedback(draggingHapticFeedback)
|
||||
}
|
||||
var fromIndex by remember { mutableIntStateOf(-1) }
|
||||
@@ -270,11 +330,12 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
tonalElevation = 1.dp,
|
||||
shadowElevation = 1.dp,
|
||||
headlineContent = {
|
||||
Text("$defaultName [${downloadsCount.getOrDefault(null, 0)}]")
|
||||
Text("$emptyLabelName [${downloadsCount.getOrDefault(null, 0)}]")
|
||||
},
|
||||
)
|
||||
}
|
||||
itemsIndexed(labelList, key = { _, item -> item.id!! }) { index, (label, _, id) ->
|
||||
|
||||
itemsIndexed(groupList, key = { _, (id) -> id }) { index, (id, label) ->
|
||||
val item by rememberUpdatedState(label)
|
||||
// Not using rememberSwipeToDismissBoxState to prevent LazyColumn from reusing it
|
||||
val dismissState = remember { SwipeToDismissBoxState(SwipeToDismissBoxValue.Settled, density, positionalThreshold = positionalThreshold) }
|
||||
@@ -297,10 +358,11 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ReorderableItem(reorderableLabelState, key = id!!) { isDragging ->
|
||||
ReorderableItem(reorderableLabelState, enabled = editEnable, key = id) { isDragging ->
|
||||
SwipeToDismissBox2(
|
||||
state = dismissState,
|
||||
backgroundContent = {},
|
||||
gesturesEnabled = editEnable,
|
||||
) {
|
||||
val elevation by animateDpAsState(
|
||||
if (isDragging) {
|
||||
@@ -318,9 +380,10 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
tonalElevation = 1.dp,
|
||||
shadowElevation = elevation,
|
||||
headlineContent = {
|
||||
Text("$item [${downloadsCount.getOrDefault(item, 0)}]")
|
||||
val name = if (filterMode == DownloadsFilterMode.ARTIST) label.translateArtist() else label
|
||||
Text("$name [${downloadsCount.getOrDefault(item, 0)}]")
|
||||
},
|
||||
trailingContent = {
|
||||
trailingContent = editEnable.ifTrueThen {
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = {
|
||||
@@ -350,7 +413,7 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
if (fromIndex != -1) {
|
||||
if (fromIndex != index) {
|
||||
val range = if (fromIndex < index) fromIndex..index else index..fromIndex
|
||||
val toUpdate = labelList.slice(range)
|
||||
val toUpdate = DownloadManager.labelList.slice(range)
|
||||
toUpdate.zip(range).forEach { it.first.position = it.second }
|
||||
launchIO { EhDB.updateDownloadLabel(toUpdate) }
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ fun SwipeToDismissBox2(
|
||||
state: SwipeToDismissBoxState,
|
||||
backgroundContent: @Composable RowScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
gesturesEnabled: Boolean = true,
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
val viewConfiguration = LocalViewConfiguration.current
|
||||
@@ -23,6 +24,7 @@ fun SwipeToDismissBox2(
|
||||
backgroundContent = backgroundContent,
|
||||
modifier = modifier,
|
||||
enableDismissFromStartToEnd = false,
|
||||
gesturesEnabled = gesturesEnabled,
|
||||
) {
|
||||
CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
|
||||
content()
|
||||
|
||||
@@ -236,6 +236,10 @@
|
||||
<string name="remember_download_label">记住下载标签</string>
|
||||
<string name="default_download_label">默认下载标签</string>
|
||||
<string name="added_to_download_list">已添加至下载列表</string>
|
||||
<string name="select_grouping_mode">选择分组依据</string>
|
||||
<string name="select_grouping_mode_custom">自定义标签</string>
|
||||
<string name="select_grouping_mode_artist">作者</string>
|
||||
<string name="unknown_artists">未知</string>
|
||||
<!-- Download label -->
|
||||
<string name="add">添加</string>
|
||||
<string name="new_label_title">新标签</string>
|
||||
|
||||
@@ -277,6 +277,10 @@
|
||||
<string name="remember_download_label">Remember download label</string>
|
||||
<string name="default_download_label">Default download label</string>
|
||||
<string name="added_to_download_list">Added to download list</string>
|
||||
<string name="select_grouping_mode">Select Grouping Mode</string>
|
||||
<string name="select_grouping_mode_custom">Custom</string>
|
||||
<string name="select_grouping_mode_artist">Artist</string>
|
||||
<string name="unknown_artists">Unknown</string>
|
||||
<!-- Download label -->
|
||||
<string name="add">Add</string>
|
||||
<string name="new_label_title">New label</string>
|
||||
|
||||
Reference in new issue
Block a user