Skip to content

Commit 1940c3f

Browse files
authored
Merge pull request #1076 from wangwang-code/patch-8-31
快照管理页批量删除支持自动快照,单卡删除保留滚动位置并修复瀑布流列归属
2 parents 0a4d88b + e297880 commit 1940c3f

2 files changed

Lines changed: 62 additions & 25 deletions

File tree

app/src/main/java/ceui/pixiv/snapshot/SnapshotListFragment.kt

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import ceui.pixiv.ui.navigation.TemplateRoute
3939
* filter == null 表示“全部”。
4040
*
4141
* 列表由正式快照(SnapshotSummary)和自动快照(AutoSnapshotSummary)合并而成:
42-
* 自动快照带“自动”标记,长按弹窗可转正/删除,多选模式下不显示勾选框。
42+
* 自动快照带“自动”标记,长按弹窗可转正/删除;批量导出多选态不显示自动快照勾选框,
43+
* 批量删除多选态允许勾选并批量删除自动快照。
4344
*/
4445
class SnapshotListFragment : Fragment() {
4546

@@ -107,13 +108,16 @@ class SnapshotListFragment : Fragment() {
107108
}
108109

109110
/**
110-
* [resetScroll] 只在导入/删除这类**结构性变更**后传 true。
111+
* [resetScroll] 只在导入/批量删除这类**结构性变更**后传 true。
112+
* 单卡删除只移除一张卡,保留当前滚动位置即可,与浏览历史页行为一致。
113+
* [reflow] 用于单卡删除后让 StaggeredGrid 重新分配 span,避免最后一张停在右列;
114+
* 不会清空列表,也不会回顶。
111115
*
112116
* 「先清空再提交 + 回顶」是为了让双列瀑布流按新顺序重新布局(否则新卡会被 Diff 塞进
113117
* 右列/旧列错位)——那是新卡进来时才需要付的代价。onResume 的例行刷新也这么干的话,
114118
* 每次从详情页返回列表都要整个闪一下并跳回顶部,快照一多就再也找不回刚才看到哪了。
115119
*/
116-
fun reload(resetScroll: Boolean = false) {
120+
fun reload(resetScroll: Boolean = false, reflow: Boolean = false) {
117121
val appContext = requireContext().applicationContext
118122
lifecycleScope.launch {
119123
val all = try {
@@ -131,16 +135,26 @@ class SnapshotListFragment : Fragment() {
131135
return@launch
132136
}
133137
if (_binding == null) return@launch
138+
val snapshotList = binding.snapshotList
134139
if (resetScroll) adapter.submitList(null)
135-
adapter.submitList(all)
140+
if (reflow) {
141+
adapter.submitList(all, Runnable {
142+
if (_binding?.snapshotList === snapshotList) {
143+
(snapshotList.layoutManager as? StaggeredGridLayoutManager)
144+
?.invalidateSpanAssignments()
145+
}
146+
})
147+
} else {
148+
adapter.submitList(all)
149+
}
136150
binding.emptyHint.isVisible = all.isEmpty()
137-
if (resetScroll) binding.snapshotList.scrollToPosition(0)
151+
if (resetScroll) snapshotList.scrollToPosition(0)
138152
}
139153
}
140154

141-
fun enterSelectionMode() {
142-
if (!hasItems()) return
143-
adapter.setSelectionMode(true)
155+
fun enterSelectionMode(includeAuto: Boolean = false) {
156+
if (!hasItems(includeAuto)) return
157+
adapter.setSelectionMode(true, includeAuto)
144158
onSelectionCountChanged?.invoke(0)
145159
}
146160

@@ -149,17 +163,24 @@ class SnapshotListFragment : Fragment() {
149163
onSelectionCountChanged?.invoke(0)
150164
}
151165

152-
/** 批量操作只包含正式快照;自动快照不可多选、不可批量导出/删除*/
166+
/** 批量导出只包含正式快照;自动快照不可导出,只参与批量删除*/
153167
fun selectedSnapshots(): List<SnapshotSummary> =
154168
adapter.currentList
155169
.filterIsInstance<FormalSnapshotCard>()
156170
.filter { it.snapshotId in adapter.selectedIds }
157171
.map { it.summary }
158172

173+
/** 批量删除可同时包含正式快照与自动快照。 */
174+
fun selectedCards(): List<SnapshotCard> =
175+
adapter.currentList.filter { it.snapshotId in adapter.selectedIds }
176+
159177
fun selectedCount(): Int = adapter.selectedIds.size
160178

161-
/** 批量选择只对正式快照有效;自动快照隐匿勾选框,不参与批量操作。 */
162-
fun hasItems(): Boolean = adapter.currentList.any { !it.isAuto }
179+
/**
180+
* 批量导出只对正式快照有效;批量删除允许自动快照,因此 [includeAuto] 时自动快照也算可选项。
181+
*/
182+
fun hasItems(includeAuto: Boolean = false): Boolean =
183+
adapter.currentList.any { includeAuto || !it.isAuto }
163184

164185
fun isAllSelected(): Boolean = adapter.isAllSelected()
165186

@@ -281,7 +302,7 @@ class SnapshotListFragment : Fragment() {
281302
}
282303
}
283304
if (ok) Common.showToast(getString(R.string.snapshot_delete_success)) else Common.showToast(getString(R.string.snapshot_delete_failed))
284-
reload(resetScroll = true)
305+
reload(reflow = true)
285306
}
286307
}
287308
.show()
@@ -310,18 +331,22 @@ private class SnapshotAdapter(
310331
var selectionMode: Boolean = false
311332
private set
312333

334+
var selectionIncludesAuto: Boolean = false
335+
private set
336+
313337
val selectedIds = linkedSetOf<String>()
314338
var onSelectionToggle: ((SnapshotCard) -> Unit)? = null
315339

316-
fun setSelectionMode(enabled: Boolean) {
317-
if (selectionMode == enabled) return
340+
fun setSelectionMode(enabled: Boolean, includeAuto: Boolean = false) {
341+
if (selectionMode == enabled && selectionIncludesAuto == includeAuto) return
318342
selectionMode = enabled
343+
selectionIncludesAuto = if (enabled) includeAuto else false
319344
if (!enabled) selectedIds.clear()
320345
notifyDataSetChanged()
321346
}
322347

323348
fun toggleSelection(card: SnapshotCard) {
324-
if (!selectionMode || card.isAuto) return
349+
if (!selectionMode || !isSelectable(card)) return
325350
if (!selectedIds.add(card.snapshotId)) {
326351
selectedIds.remove(card.snapshotId)
327352
}
@@ -332,7 +357,7 @@ private class SnapshotAdapter(
332357
fun selectAll() {
333358
if (!selectionMode) return
334359
selectedIds.clear()
335-
selectedIds.addAll(currentList.filter { !it.isAuto }.map { it.snapshotId })
360+
selectedIds.addAll(currentList.filter(::isSelectable).map { it.snapshotId })
336361
notifyDataSetChanged()
337362
}
338363

@@ -341,8 +366,11 @@ private class SnapshotAdapter(
341366
notifyDataSetChanged()
342367
}
343368

369+
private fun isSelectable(card: SnapshotCard): Boolean =
370+
!card.isAuto || selectionIncludesAuto
371+
344372
fun isAllSelected(): Boolean {
345-
val selectable = currentList.count { !it.isAuto }
373+
val selectable = currentList.count(::isSelectable)
346374
return selectable > 0 && selectedIds.size == selectable
347375
}
348376

@@ -360,6 +388,7 @@ private class SnapshotAdapter(
360388
onDelete = onDelete,
361389
onLongPress = onLongPress,
362390
selectionMode = selectionMode,
391+
selectable = isSelectable(card),
363392
selected = card.snapshotId in selectedIds,
364393
onToggleSelection = { toggleSelection(card) },
365394
)
@@ -391,6 +420,7 @@ private class SnapshotViewHolder(
391420
onDelete: (SnapshotCard) -> Unit,
392421
onLongPress: (SnapshotCard) -> Unit,
393422
selectionMode: Boolean,
423+
selectable: Boolean,
394424
selected: Boolean,
395425
onToggleSelection: () -> Unit,
396426
) {
@@ -426,11 +456,11 @@ private class SnapshotViewHolder(
426456
binding.badgeRow.isVisible = !selectionMode
427457

428458
HistorySelectBadge.bindSelection(binding.selectCheck, binding.deleteButton, selectionMode, selected)
429-
// 自动快照在多选态隐匿勾选框,且不可被点选进入批量操作
430-
binding.selectCheck.isVisible = selectionMode && !card.isAuto
459+
// 自动快照仅在批量导出多选态隐匿勾选框;批量删除多选态可勾选并批量删除
460+
binding.selectCheck.isVisible = selectionMode && selectable
431461

432462
if (selectionMode) {
433-
if (card.isAuto) {
463+
if (!selectable) {
434464
binding.root.setOnClickListener(null)
435465
binding.root.setOnLongClickListener(null)
436466
binding.deleteButton.setOnClickListener(null)

app/src/main/java/ceui/pixiv/snapshot/SnapshotManagerFragment.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import timber.log.Timber
2525
/**
2626
* 离线快照管理页:仿照 FragmentHistoryTabs,使用 viewpager_with_tablayout。
2727
* Three tabs: 全部 | 插画 | 漫画,每个 tab 是双列瀑布流卡片。
28-
* 支持多选批量导出到 SAF 文件夹。
28+
* 支持多选批量导出到 SAF 文件夹,也支持多选批量删除(含自动快照)
2929
*/
3030
class SnapshotManagerFragment : Fragment() {
3131

@@ -164,15 +164,16 @@ class SnapshotManagerFragment : Fragment() {
164164
private fun enterSelectionMode(purpose: SelectionPurpose) {
165165
if (inSelectionMode) return
166166
val tab = currentSnapshotTab() ?: return
167-
if (!tab.hasItems()) {
167+
val includeAuto = purpose == SelectionPurpose.DELETE
168+
if (!tab.hasItems(includeAuto)) {
168169
Common.showToast(getString(R.string.snapshot_empty))
169170
return
170171
}
171172
inSelectionMode = true
172173
selectionPurpose = purpose
173174
activeSelectionTab = tab
174175
selectionBackCallback.isEnabled = true
175-
tab.enterSelectionMode()
176+
tab.enterSelectionMode(includeAuto)
176177
tab.onSelectionCountChanged = { refreshSelectionToolbar() }
177178
applySelectionToolbar()
178179
}
@@ -229,7 +230,7 @@ class SnapshotManagerFragment : Fragment() {
229230

230231
private fun confirmDeleteSelected() {
231232
val tab = activeSelectionTab ?: return
232-
val items = tab.selectedSnapshots()
233+
val items = tab.selectedCards()
233234
if (items.isEmpty()) return
234235
WitDialog.MessageDialogBuilder(requireContext())
235236
.setTitle(R.string.snapshot_batch_delete)
@@ -243,7 +244,13 @@ class SnapshotManagerFragment : Fragment() {
243244
val appContext = requireContext().applicationContext
244245
lifecycleScope.launch {
245246
withContext(Dispatchers.IO) {
246-
items.forEach { SnapshotRepository.delete(appContext, it.manifest.snapshotId) }
247+
items.forEach {
248+
if (it.isAuto) {
249+
AutoSnapshotRepository.deleteAuto(appContext, it.snapshotId)
250+
} else {
251+
SnapshotRepository.delete(appContext, it.snapshotId)
252+
}
253+
}
247254
}
248255
exitSelectionMode()
249256
reloadAllTabs(resetScroll = true)

0 commit comments

Comments
 (0)