Skip to content

Commit f88e8db

Browse files
committed
release: v6.1 统一管理界面风格与体验,优化R2/KV功能
1 parent 53c3b67 commit f88e8db

8 files changed

Lines changed: 295 additions & 64 deletions

File tree

RELEASE_NOTES_v6.0.md

Lines changed: 0 additions & 13 deletions
This file was deleted.

RELEASE_NOTES_v6.1.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# CloudFlare Assistant v6.1
2+
3+
## 主要更新内容
4+
5+
- 全部管理界面(路由/域名、KV存储、R2存储)统一为Material Design 3风格,两栏布局,卡片与分割线风格一致。
6+
- R2存储界面:
7+
- 左侧为存储桶列表,右侧为对象列表。
8+
- 支持点击存储桶自动刷新对象列表。
9+
- 对象列表右下角+号按钮支持任意类型文件上传,自动处理MIME类型。
10+
- 对象卡片点击弹窗显示详情(文件名、大小、URL、自定义域),支持复制、更多、返回。
11+
- “更多”弹窗只保留复制URL、删除、返回。
12+
- KV存储界面:
13+
- 右侧标题格式与R2对象一致,动态显示“键值对(namespaceName)”。
14+
- 其它细节如空态提示、按钮可见性等与R2界面保持一致。
15+
- 体验优化:
16+
- 统一FAB按钮、卡片、分割线、弹窗等Material风格。
17+
- 修复对象详情/更多弹窗返回逻辑,避免弹出旧式对话框。
18+
- 代码结构优化,适配器支持点击事件回调,弹窗与主界面联动。
19+
20+
---
21+
22+
如需详细diff或单项说明请查阅项目代码。

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ android {
3030
applicationId = "com.muort.upworker"
3131
minSdk = 26
3232
targetSdk = 34
33-
versionCode = 2025122202
34-
versionName = "6.0"
33+
versionCode = 2025122203
34+
versionName = "6.1"
3535

3636
vectorDrawables {
3737
useSupportLibrary = true

app/src/main/java/com/muort/upworker/feature/kv/KvFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ class KvFragment : Fragment() {
111111
kvViewModel.selectedNamespace.collect { namespace ->
112112
binding.fabAddKey.visibility = if (namespace != null) View.VISIBLE else View.GONE
113113
binding.keyEmptyText.text = if (namespace != null) "暂无键值对\n点击 + 添加" else "请先选择命名空间"
114-
// 动态更新右侧标题栏
115-
binding.keyTitleText.text = namespace?.title ?: getString(R.string.kv_key_value)
114+
// 动态更新右侧标题栏,格式:键值对(namespaceName)
115+
binding.keyTitleText.text = namespace?.let { "键值对(${it.title}" } ?: getString(R.string.kv_key_value)
116116
}
117117
}
118118

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.muort.upworker.feature.r2
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.recyclerview.widget.RecyclerView
6+
import com.muort.upworker.core.model.R2Object
7+
import com.muort.upworker.databinding.ItemR2ObjectBinding
8+
9+
class ObjectAdapter : RecyclerView.Adapter<ObjectAdapter.ViewHolder>() {
10+
private var objects = listOf<R2Object>()
11+
private var onObjectClick: ((R2Object) -> Unit)? = null
12+
13+
fun submitList(newList: List<R2Object>) {
14+
objects = newList
15+
notifyDataSetChanged()
16+
}
17+
18+
fun setOnObjectClickListener(listener: (R2Object) -> Unit) {
19+
onObjectClick = listener
20+
}
21+
22+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
23+
val binding = ItemR2ObjectBinding.inflate(LayoutInflater.from(parent.context), parent, false)
24+
return ViewHolder(binding, onObjectClick)
25+
}
26+
27+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
28+
holder.bind(objects[position])
29+
}
30+
31+
override fun getItemCount() = objects.size
32+
33+
class ViewHolder(
34+
private val binding: ItemR2ObjectBinding,
35+
private val onObjectClick: ((R2Object) -> Unit)?
36+
) : RecyclerView.ViewHolder(binding.root) {
37+
fun bind(obj: R2Object) {
38+
binding.objectKeyText.text = obj.key
39+
binding.objectSizeText.text = obj.size?.let { "${it} B" } ?: "-"
40+
binding.root.setOnClickListener {
41+
onObjectClick?.invoke(obj)
42+
}
43+
}
44+
}
45+
}

app/src/main/java/com/muort/upworker/feature/r2/R2Fragment.kt

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class R2Fragment : Fragment() {
4242
private val r2ViewModel: R2ViewModel by viewModels()
4343

4444
private lateinit var bucketAdapter: BucketAdapter
45+
private lateinit var objectAdapter: ObjectAdapter
4546
private var currentBucket: R2Bucket? = null
4647
private var downloadData: ByteArray? = null
4748

@@ -78,9 +79,11 @@ class R2Fragment : Fragment() {
7879
super.onViewCreated(view, savedInstanceState)
7980

8081
setupAdapter()
82+
setupObjectAdapter()
8183
setupClickListeners()
8284
observeViewModel()
83-
85+
// 初始对象标题
86+
binding.objectTitleText.text = "对象"
8487
accountViewModel.defaultAccount.value?.let { account ->
8588
r2ViewModel.loadBuckets(account)
8689
}
@@ -94,7 +97,10 @@ class R2Fragment : Fragment() {
9497
r2ViewModel.loadObjects(account, bucket.name)
9598
r2ViewModel.loadCustomDomains(account, bucket.name)
9699
}
97-
showObjectsDialog(bucket)
100+
// 设置右侧对象标题为当前存储桶名
101+
binding.objectTitleText.text = "对象(${bucket.name}"
102+
// 清空对象列表等待新数据
103+
objectAdapter.submitList(emptyList())
98104
},
99105
onDeleteClick = { bucket ->
100106
showDeleteBucketDialog(bucket)
@@ -105,11 +111,32 @@ class R2Fragment : Fragment() {
105111
)
106112
binding.bucketRecyclerView.adapter = bucketAdapter
107113
}
114+
115+
private fun setupObjectAdapter() {
116+
objectAdapter = ObjectAdapter()
117+
objectAdapter.setOnObjectClickListener { obj ->
118+
val bucket = r2ViewModel.selectedBucket.value
119+
val account = accountViewModel.defaultAccount.value
120+
if (bucket != null && account != null) {
121+
showObjectDetailsDialog(account, bucket, obj, r2ViewModel.customDomains.value)
122+
}
123+
}
124+
binding.objectRecyclerView.adapter = objectAdapter
125+
}
108126

109127
private fun setupClickListeners() {
110128
binding.fabAddBucket.setOnClickListener {
111129
showAddBucketDialog()
112130
}
131+
binding.fabAddObject.setOnClickListener {
132+
// 仅允许在选中存储桶时上传
133+
val bucket = r2ViewModel.selectedBucket.value
134+
if (bucket != null) {
135+
selectFileToUpload(bucket)
136+
} else {
137+
Snackbar.make(binding.root, "请先选择存储桶", Snackbar.LENGTH_SHORT).show()
138+
}
139+
}
113140
}
114141

115142
private fun observeViewModel() {
@@ -122,6 +149,21 @@ class R2Fragment : Fragment() {
122149
if (buckets.isEmpty()) View.VISIBLE else View.GONE
123150
}
124151
}
152+
153+
launch {
154+
r2ViewModel.objects.collect { objects ->
155+
objectAdapter.submitList(objects)
156+
binding.objectEmptyText.visibility =
157+
if (objects.isEmpty()) View.VISIBLE else View.GONE
158+
}
159+
}
160+
161+
launch {
162+
r2ViewModel.selectedBucket.collect { bucket ->
163+
// 选中存储桶时显示fabAddObject,否则隐藏
164+
binding.fabAddObject.visibility = if (bucket != null) View.VISIBLE else View.GONE
165+
}
166+
}
125167

126168
launch {
127169
r2ViewModel.loadingState.collect { isLoading ->
@@ -283,14 +325,9 @@ class R2Fragment : Fragment() {
283325
MaterialAlertDialogBuilder(requireContext())
284326
.setTitle(title)
285327
.setMessage(message)
286-
.setPositiveButton("返回") { _, _ ->
287-
// Return to objects list
288-
showObjectsListDialog(account, bucket)
289-
}
328+
.setPositiveButton("返回", null)
290329
.setNeutralButton(if (customUrl != null) "复制自定义域 URL" else "复制 URL") { _, _ ->
291330
copyToClipboard(customUrl ?: defaultUrl, if (customUrl != null) "自定义域 URL 已复制" else "URL 已复制")
292-
// Return to objects list after copying
293-
showObjectsListDialog(account, bucket)
294331
}
295332
.setNegativeButton("更多") { _, _ ->
296333
// Show more options
@@ -307,33 +344,27 @@ class R2Fragment : Fragment() {
307344
} else {
308345
options.add("复制 URL")
309346
}
310-
options.add("下载")
311347
options.add("删除")
312-
348+
313349
MaterialAlertDialogBuilder(requireContext())
314350
.setTitle("操作")
315351
.setItems(options.toTypedArray()) { _, which ->
316352
var index = 0
317353
when {
318354
customUrl != null && which == index++ -> {
319355
copyToClipboard(customUrl, "自定义域 URL 已复制")
320-
showObjectsListDialog(account, bucket)
321356
}
322357
which == index++ -> {
323358
copyToClipboard(if (customUrl != null) defaultUrl else defaultUrl, if (customUrl != null) "默认 URL 已复制" else "URL 已复制")
324-
showObjectsListDialog(account, bucket)
325-
}
326-
which == index++ -> {
327-
downloadObject(bucket, obj)
328-
showObjectsListDialog(account, bucket)
329359
}
330360
which == index -> {
331361
showDeleteObjectDialog(bucket, obj)
332362
}
333363
}
334364
}
335365
.setNegativeButton("返回") { _, _ ->
336-
showObjectsListDialog(account, bucket)
366+
// 返回对象详情弹窗
367+
showObjectDetailsDialog(account, bucket, obj, r2ViewModel.customDomains.value)
337368
}
338369
.show()
339370
}

0 commit comments

Comments
 (0)