Support adding favorite notes
This commit is contained in:
1 parent
325909401c
commit
4b66f58fd3
8 files changed
+92
-53
No files matched your search
@@ -59,7 +59,6 @@ object Settings : DataStorePreferences(null) {
|
||||
var favLocalCount by intPref("fav_local", 0)
|
||||
var favCloudCount by intPref("fav_cloud", 0)
|
||||
var recentFavCat by intPref("recent_fav_cat", FavListUrlBuilder.FAV_CAT_ALL)
|
||||
var defaultFavSlot by intPref("default_favorite_2", -2) // -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
|
||||
var securityDelay by intPref("require_unlock_delay", 0)
|
||||
var clipboardTextHashCode by intPref("clipboard_text_hash_code", 0)
|
||||
var listThumbSize by intPref("list_tile_size", 40)
|
||||
|
||||
@@ -239,7 +239,7 @@ object EhEngine {
|
||||
gid: Long,
|
||||
token: String?,
|
||||
dstCat: Int = -1,
|
||||
note: String? = null,
|
||||
note: String = "",
|
||||
) {
|
||||
val catStr: String = when (dstCat) {
|
||||
-1 -> "favdel"
|
||||
@@ -250,7 +250,7 @@ object EhEngine {
|
||||
return ehRequest(url, url, EhUrl.origin) {
|
||||
formBody {
|
||||
add("favcat", catStr)
|
||||
add("favnote", note ?: "")
|
||||
add("favnote", note)
|
||||
// apply=Add+to+Favorites is not necessary, just use apply=Apply+Changes all the time
|
||||
add("apply", "Apply Changes")
|
||||
add("update", "1")
|
||||
|
||||
@@ -54,6 +54,7 @@ import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.lang.launchNow
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
@@ -65,6 +66,7 @@ object CommonOperations {
|
||||
startDownload(activity!!, listOf(galleryInfo), forceDefault)
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun startDownload(
|
||||
activity: MainActivity,
|
||||
galleryInfos: List<GalleryInfo>,
|
||||
@@ -130,7 +132,7 @@ object CommonOperations {
|
||||
val list = DownloadManager.labelList
|
||||
val items = mutableListOf<String>()
|
||||
items.add(activity.getString(R.string.default_download_label_name))
|
||||
items.addAll(list.mapNotNull { it.label })
|
||||
items.addAll(list.map { it.label })
|
||||
ListCheckBoxDialogBuilder(
|
||||
activity,
|
||||
items,
|
||||
@@ -193,8 +195,8 @@ suspend fun keepNoMediaFileStatus() {
|
||||
}
|
||||
|
||||
suspend fun DialogState.addToFavorites(galleryInfo: GalleryInfo): Boolean {
|
||||
val localFaved = EhDB.containLocalFavorites(galleryInfo.gid)
|
||||
val localFav = if (localFaved) {
|
||||
val localFavorited = EhDB.containLocalFavorites(galleryInfo.gid)
|
||||
val localFav = if (localFavorited) {
|
||||
Icons.Default.Favorite
|
||||
} else {
|
||||
Icons.Default.FavoriteBorder
|
||||
@@ -206,58 +208,58 @@ suspend fun DialogState.addToFavorites(galleryInfo: GalleryInfo): Boolean {
|
||||
Icons.Default.FavoriteBorder
|
||||
} to name
|
||||
}.toTypedArray()
|
||||
val slot = showSelectItemWithIcon(localFav, *items, title = R.string.add_favorites_dialog_title) - 1
|
||||
val newFavoriteName = Settings.favCat.getOrNull(slot)
|
||||
return doAddToFavorites(galleryInfo, slot, newFavoriteName, localFaved = localFaved)
|
||||
val isFavorited = galleryInfo.favoriteSlot != NOT_FAVORITED
|
||||
val (slot, note) = if (isFavorited) {
|
||||
val remove = Icons.Default.HeartBroken to appCtx.getString(R.string.remove_from_favourites)
|
||||
showSelectItemWithIconAndTextField(remove, localFav, *items, title = R.string.add_favorites_dialog_title, hint = R.string.favorite_note, maxChar = MAX_FAVNOTE_CHAR)
|
||||
} else {
|
||||
showSelectItemWithIconAndTextField(localFav, *items, title = R.string.add_favorites_dialog_title, hint = R.string.favorite_note, maxChar = MAX_FAVNOTE_CHAR)
|
||||
}
|
||||
return doAddToFavorites(galleryInfo, if (isFavorited) slot - 2 else slot - 1, note, localFavorited)
|
||||
}
|
||||
|
||||
private suspend fun doAddToFavorites(
|
||||
galleryInfo: GalleryInfo,
|
||||
slot: Int = NOT_FAVORITED,
|
||||
newFavoriteName: String? = null,
|
||||
note: String? = null,
|
||||
localFaved: Boolean = true,
|
||||
note: String = "",
|
||||
localFavorited: Boolean = true,
|
||||
): Boolean {
|
||||
val add = when (slot) {
|
||||
NOT_FAVORITED -> {
|
||||
EhDB.removeLocalFavorites(galleryInfo.gid)
|
||||
NOT_FAVORITED -> { // Remove from cloud favorites first
|
||||
if (galleryInfo.favoriteSlot > LOCAL_FAVORITED) {
|
||||
EhEngine.addFavorites(galleryInfo.gid, galleryInfo.token)
|
||||
} else if (localFavorited) {
|
||||
EhDB.removeLocalFavorites(galleryInfo.gid)
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
LOCAL_FAVORITED -> {
|
||||
if (localFaved) {
|
||||
if (localFavorited) {
|
||||
EhDB.removeLocalFavorites(galleryInfo.gid)
|
||||
} else {
|
||||
EhDB.putLocalFavorites(galleryInfo)
|
||||
}
|
||||
!localFaved
|
||||
!localFavorited
|
||||
}
|
||||
|
||||
in 0..9 -> {
|
||||
if (galleryInfo.favoriteSlot == slot) {
|
||||
EhEngine.addFavorites(galleryInfo.gid, galleryInfo.token)
|
||||
false
|
||||
} else {
|
||||
EhEngine.addFavorites(galleryInfo.gid, galleryInfo.token, slot, note)
|
||||
true
|
||||
}
|
||||
EhEngine.addFavorites(galleryInfo.gid, galleryInfo.token, slot, note)
|
||||
true
|
||||
}
|
||||
|
||||
else -> throw EhException("Invalid favorite slot!")
|
||||
}
|
||||
if (add) {
|
||||
if (slot != LOCAL_FAVORITED || galleryInfo.favoriteSlot == NOT_FAVORITED) { // Cloud favorites have priority
|
||||
if (add) { // Cloud favorites have priority
|
||||
if (slot != LOCAL_FAVORITED || galleryInfo.favoriteSlot == NOT_FAVORITED) {
|
||||
galleryInfo.favoriteSlot = slot
|
||||
galleryInfo.favoriteName = newFavoriteName
|
||||
galleryInfo.favoriteName = Settings.favCat.getOrNull(slot)
|
||||
withUIContext {
|
||||
FavouriteStatusRouter.modifyFavourites(galleryInfo.gid, slot)
|
||||
}
|
||||
}
|
||||
} else if (slot != LOCAL_FAVORITED || galleryInfo.favoriteSlot == LOCAL_FAVORITED) {
|
||||
val newSlot = if (slot > LOCAL_FAVORITED && localFaved) LOCAL_FAVORITED else NOT_FAVORITED
|
||||
val newSlot = if (galleryInfo.favoriteSlot > LOCAL_FAVORITED && localFavorited) LOCAL_FAVORITED else NOT_FAVORITED
|
||||
galleryInfo.favoriteSlot = newSlot
|
||||
galleryInfo.favoriteName = null
|
||||
withUIContext {
|
||||
@@ -329,6 +331,8 @@ suspend fun DialogState.doGalleryInfoAction(info: GalleryInfo, context: Context)
|
||||
}
|
||||
}
|
||||
|
||||
private const val MAX_FAVNOTE_CHAR = 200
|
||||
|
||||
suspend fun DialogState.confirmRemoveDownload(info: GalleryInfo): Boolean = show(
|
||||
confirmText = android.R.string.ok,
|
||||
dismissText = android.R.string.cancel,
|
||||
@@ -338,7 +342,7 @@ suspend fun DialogState.confirmRemoveDownload(info: GalleryInfo): Boolean = show
|
||||
|
||||
suspend fun DialogState.showMoveDownloadLabel(info: GalleryInfo) {
|
||||
val defaultLabel = appCtx.getString(R.string.default_download_label_name)
|
||||
val labels = DownloadManager.labelList.mapNotNull { it.label }.toTypedArray()
|
||||
val labels = DownloadManager.labelList.map { it.label }.toTypedArray()
|
||||
val selected = showSelectItem(defaultLabel, *labels, title = R.string.download_move_dialog_title)
|
||||
val downloadInfo = DownloadManager.getDownloadInfo(info.gid) ?: return
|
||||
val label = if (selected == 0) null else labels[selected - 1]
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.content.DialogInterface
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.GravityCompat
|
||||
@@ -322,25 +321,6 @@ class FavoritesScene :
|
||||
): View {
|
||||
val drawerBinding = DrawerListRvBinding.inflate(inflater, container, false)
|
||||
drawerBinding.toolbar.setTitle(R.string.collections)
|
||||
drawerBinding.toolbar.inflateMenu(R.menu.drawer_favorites)
|
||||
drawerBinding.toolbar.setOnMenuItemClickListener { item: MenuItem ->
|
||||
val id = item.itemId
|
||||
if (id == R.id.action_default_favorites_slot) {
|
||||
val items = arrayOfNulls<String>(12)
|
||||
items[0] = getString(R.string.let_me_select)
|
||||
items[1] = getString(R.string.local_favorites)
|
||||
val favCat = Settings.favCat
|
||||
System.arraycopy(favCat, 0, items, 2, 10)
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setTitle(R.string.default_favorites_collection)
|
||||
.setItems(items) { _, which ->
|
||||
Settings.defaultFavSlot = which - 2
|
||||
}
|
||||
.show()
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
drawerBinding.recyclerViewDrawer.layoutManager = LinearLayoutManager(context)
|
||||
mDrawerAdapter = FavDrawerAdapter(inflater)
|
||||
drawerBinding.recyclerViewDrawer.adapter = mDrawerAdapter
|
||||
|
||||
@@ -16,6 +16,7 @@ private const val QuarterCircle = PI / 2
|
||||
fun CircularLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
radius: Dp,
|
||||
placeFirstItemInCenter: Boolean = false,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Layout(
|
||||
@@ -25,13 +26,24 @@ fun CircularLayout(
|
||||
val radiusPx = radius.toPx()
|
||||
val itemConstraints = constraints.copy(minWidth = 0, minHeight = 0)
|
||||
val placeables = measurables.map { measurable -> measurable.measure(itemConstraints) }
|
||||
val theta = FullCircle / (placeables.count())
|
||||
val outPlaceables = if (placeFirstItemInCenter) placeables.drop(1) else placeables
|
||||
val theta = FullCircle / (outPlaceables.count())
|
||||
|
||||
layout(
|
||||
width = constraints.minWidth,
|
||||
height = constraints.minHeight,
|
||||
) {
|
||||
placeables.forEachIndexed { i, it ->
|
||||
if (placeFirstItemInCenter) {
|
||||
placeables[0].let {
|
||||
val centerOffsetX = constraints.maxWidth / 2 - it.width / 2
|
||||
val centerOffsetY = constraints.maxHeight / 2 - it.height / 2
|
||||
it.place(
|
||||
x = centerOffsetX,
|
||||
y = centerOffsetY,
|
||||
)
|
||||
}
|
||||
}
|
||||
outPlaceables.forEachIndexed { i, it ->
|
||||
val centerOffsetX = constraints.maxWidth / 2 - it.width / 2
|
||||
val centerOffsetY = constraints.maxHeight / 2 - it.height / 2
|
||||
val offsetX = radiusPx * cos(theta * i - QuarterCircle) + centerOffsetX
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.hippo.ehviewer.ui.tools
|
||||
|
||||
import android.graphics.Typeface
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -13,15 +14,21 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ShapeDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -31,8 +38,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
@@ -186,19 +197,50 @@ class DialogState {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun showSelectItemWithIcon(
|
||||
suspend fun showSelectItemWithIconAndTextField(
|
||||
vararg items: Pair<ImageVector, String>,
|
||||
@StringRes title: Int,
|
||||
): Int = showNoButton(false) {
|
||||
@StringRes hint: Int,
|
||||
maxChar: Int,
|
||||
): Pair<Int, String> = showNoButton(false) {
|
||||
Column {
|
||||
Text(text = stringResource(id = title), modifier = Modifier.padding(horizontal = 16.dp).padding(top = 16.dp), style = MaterialTheme.typography.titleMedium)
|
||||
CircularLayout(
|
||||
radius = 112.dp,
|
||||
modifier = Modifier.fillMaxWidth().aspectRatio(1F),
|
||||
placeFirstItemInCenter = true,
|
||||
) {
|
||||
var note by remember { mutableStateOf("") }
|
||||
TextField(
|
||||
value = note,
|
||||
onValueChange = { note = it },
|
||||
modifier = Modifier.width(128.dp),
|
||||
textStyle = TextStyle(fontFamily = FontFamily(Typeface.MONOSPACE)),
|
||||
label = { Text(text = stringResource(id = hint)) },
|
||||
trailingIcon = {
|
||||
if (note.isNotEmpty()) {
|
||||
IconButton(onClick = { note = "" }) {
|
||||
Icon(imageVector = Icons.Default.Close, contentDescription = null)
|
||||
}
|
||||
}
|
||||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = "${note.length} / $maxChar",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
},
|
||||
maxLines = 6,
|
||||
shape = ShapeDefaults.ExtraSmall,
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
)
|
||||
items.forEachIndexed { index, (icon, text) ->
|
||||
Column(
|
||||
modifier = Modifier.clip(IconWithTextCorner).clickable { dismissWith(index) },
|
||||
modifier = Modifier.clip(IconWithTextCorner).clickable { dismissWith(index to note) },
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(imageVector = icon, contentDescription = null, tint = AlertDialogDefaults.iconContentColor)
|
||||
|
||||
@@ -414,6 +414,7 @@
|
||||
<string name="move_favorites_dialog_title">移动收藏</string>
|
||||
<string name="default_favorites_collection">默认收藏夹</string>
|
||||
<string name="let_me_select">让我选择</string>
|
||||
<string name="favorite_note">备注</string>
|
||||
<string name="collections">收藏夹</string>
|
||||
<string name="need_sign_in">需要登录</string>
|
||||
<!-- Progress Scene -->
|
||||
|
||||
@@ -472,6 +472,7 @@
|
||||
<string name="move_favorites_dialog_title">Move favorites</string>
|
||||
<string name="default_favorites_collection">Default favorites collection</string>
|
||||
<string name="let_me_select">Let me select</string>
|
||||
<string name="favorite_note">Note</string>
|
||||
<string name="collections">Collections</string>
|
||||
<string name="need_sign_in">Signing in is required</string>
|
||||
<!-- Progress Scene -->
|
||||
|
||||
Reference in new issue
Block a user