@@ -39,7 +39,8 @@ import ceui.pixiv.ui.navigation.TemplateRoute
3939 * filter == null 表示“全部”。
4040 *
4141 * 列表由正式快照(SnapshotSummary)和自动快照(AutoSnapshotSummary)合并而成:
42- * 自动快照带“自动”标记,长按弹窗可转正/删除,多选模式下不显示勾选框。
42+ * 自动快照带“自动”标记,长按弹窗可转正/删除;批量导出多选态不显示自动快照勾选框,
43+ * 批量删除多选态允许勾选并批量删除自动快照。
4344 */
4445class 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 )
0 commit comments