Skip to content

Commit 55c73d4

Browse files
committed
feat: Miku profile cards
1 parent d0f0b9a commit 55c73d4

10 files changed

Lines changed: 139 additions & 74 deletions

File tree

app/src/main/java/top/uwu/mikubox/ui/MainActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,11 @@ class MainActivity : AppCompatActivity() {
165165

166166
private fun pull(profile: MihomoProfileStore.Profile) {
167167
lifecycleScope.launch {
168+
adapter.setUpdating(profile.id)
168169
val result = withContext(Dispatchers.IO) {
169170
runCatching { MihomoSubscriptionUpdater.update(this@MainActivity, profile) }
170171
}
172+
adapter.setUpdating(null)
171173
result
172174
.onSuccess { toast(getString(R.string.toast_subscription_updated)) }
173175
.onFailure { toast(getString(R.string.toast_update_failed, it.message ?: "")) }

app/src/main/java/top/uwu/mikubox/ui/ProfileAdapter.kt

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import android.view.LayoutInflater
44
import android.view.View
55
import android.view.ViewGroup
66
import android.widget.TextView
7+
import androidx.appcompat.widget.PopupMenu
78
import androidx.recyclerview.widget.RecyclerView
89
import com.google.android.material.button.MaterialButton
910
import com.google.android.material.card.MaterialCardView
11+
import com.google.android.material.progressindicator.LinearProgressIndicator
1012
import top.uwu.mikubox.R
1113
import top.uwu.mikubox.profile.MihomoProfileStore
1214
import top.uwu.mikubox.profile.MihomoTrafficStore
@@ -20,6 +22,7 @@ class ProfileAdapter(
2022

2123
private var profiles: List<MihomoProfileStore.Profile> = emptyList()
2224
private var selectedId: String? = null
25+
private var updatingId: String? = null
2326

2427
@SuppressWarnings("NotifyDataSetChanged")
2528
fun submit(list: List<MihomoProfileStore.Profile>, selectedId: String?) {
@@ -28,27 +31,34 @@ class ProfileAdapter(
2831
notifyDataSetChanged()
2932
}
3033

34+
/** Show/hide the indeterminate progress bar on the card being refreshed. */
35+
@SuppressWarnings("NotifyDataSetChanged")
36+
fun setUpdating(profileId: String?) {
37+
this.updatingId = profileId
38+
notifyDataSetChanged()
39+
}
40+
3141
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH =
3242
VH(LayoutInflater.from(parent.context).inflate(R.layout.item_profile, parent, false))
3343

3444
override fun getItemCount(): Int = profiles.size
3545

3646
override fun onBindViewHolder(holder: VH, position: Int) {
3747
val profile = profiles[position]
38-
holder.bind(profile, profile.id == selectedId)
48+
holder.bind(profile, profile.id == selectedId, profile.id == updatingId)
3949
}
4050

4151
inner class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
4252
private val card = itemView as MaterialCardView
53+
private val progress: LinearProgressIndicator = itemView.findViewById(R.id.update_progress)
4354
private val selectedBar: View = itemView.findViewById(R.id.selected_view)
4455
private val name: TextView = itemView.findViewById(R.id.tv_name)
4556
private val meta: TextView = itemView.findViewById(R.id.tv_meta)
4657
private val traffic: TextView = itemView.findViewById(R.id.tv_traffic)
47-
private val share: MaterialButton = itemView.findViewById(R.id.btn_share)
4858
private val update: MaterialButton = itemView.findViewById(R.id.btn_update)
49-
private val delete: MaterialButton = itemView.findViewById(R.id.btn_delete)
59+
private val overflow: MaterialButton = itemView.findViewById(R.id.btn_overflow)
5060

51-
fun bind(profile: MihomoProfileStore.Profile, selected: Boolean) {
61+
fun bind(profile: MihomoProfileStore.Profile, selected: Boolean, updating: Boolean) {
5262
val ctx = itemView.context
5363
name.text = profile.name
5464
val type = ctx.getString(
@@ -70,12 +80,27 @@ class ProfileAdapter(
7080
} else {
7181
traffic.visibility = View.GONE
7282
}
83+
progress.visibility = if (updating) View.VISIBLE else View.GONE
7384
selectedBar.visibility = if (selected) View.VISIBLE else View.INVISIBLE
7485
update.visibility = if (profile.isSubscription) View.VISIBLE else View.GONE
7586
card.setOnClickListener { onSelect(profile) }
76-
share.setOnClickListener { onShare(profile) }
7787
update.setOnClickListener { onUpdate(profile) }
78-
delete.setOnClickListener { onDelete(profile) }
88+
overflow.setOnClickListener { showMenu(it, profile) }
89+
}
90+
91+
private fun showMenu(anchor: View, profile: MihomoProfileStore.Profile) {
92+
PopupMenu(anchor.context, anchor).apply {
93+
menuInflater.inflate(R.menu.menu_profile, menu)
94+
setOnMenuItemClickListener { item ->
95+
when (item.itemId) {
96+
R.id.action_select -> onSelect(profile)
97+
R.id.action_share -> onShare(profile)
98+
R.id.action_delete -> onDelete(profile)
99+
}
100+
true
101+
}
102+
show()
103+
}
79104
}
80105

81106
private fun formatBytes(bytes: Long): String {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="24"
5+
android:viewportHeight="24"
6+
android:tint="?attr/colorControlNormal">
7+
<path
8+
android:fillColor="@android:color/white"
9+
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
10+
</vector>

app/src/main/res/layout/item_profile.xml

Lines changed: 74 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,85 +16,91 @@
1616
<LinearLayout
1717
android:layout_width="match_parent"
1818
android:layout_height="wrap_content"
19-
android:gravity="center_vertical"
20-
android:orientation="horizontal">
19+
android:orientation="vertical">
2120

22-
<View
23-
android:id="@+id/selected_view"
24-
android:layout_width="4dp"
25-
android:layout_height="match_parent"
26-
android:layout_marginVertical="10dp"
27-
android:background="?attr/colorPrimary"
28-
android:visibility="invisible"
21+
<com.google.android.material.progressindicator.LinearProgressIndicator
22+
android:id="@+id/update_progress"
23+
android:layout_width="match_parent"
24+
android:layout_height="wrap_content"
25+
android:indeterminate="true"
26+
android:visibility="gone"
27+
app:indicatorColor="?attr/colorPrimary"
2928
tools:visibility="visible" />
3029

3130
<LinearLayout
32-
android:layout_width="0dp"
31+
android:layout_width="match_parent"
3332
android:layout_height="wrap_content"
34-
android:layout_weight="1"
35-
android:orientation="vertical"
36-
android:paddingStart="14dp"
37-
android:paddingTop="12dp"
38-
android:paddingEnd="8dp"
39-
android:paddingBottom="12dp">
33+
android:gravity="center_vertical"
34+
android:orientation="horizontal">
4035

41-
<TextView
42-
android:id="@+id/tv_name"
43-
android:layout_width="match_parent"
44-
android:layout_height="wrap_content"
45-
android:ellipsize="end"
46-
android:maxLines="1"
47-
android:textColor="?attr/colorOnSurface"
48-
android:textSize="16sp"
49-
android:textStyle="bold"
50-
tools:text="My Subscription" />
36+
<View
37+
android:id="@+id/selected_view"
38+
android:layout_width="4dp"
39+
android:layout_height="match_parent"
40+
android:layout_marginVertical="12dp"
41+
android:background="?attr/colorPrimary"
42+
android:visibility="invisible"
43+
tools:visibility="visible" />
5144

52-
<TextView
53-
android:id="@+id/tv_meta"
54-
android:layout_width="match_parent"
45+
<LinearLayout
46+
android:layout_width="0dp"
5547
android:layout_height="wrap_content"
56-
android:layout_marginTop="2dp"
57-
android:textColor="?attr/colorOnSurfaceVariant"
58-
android:textSize="12sp"
59-
tools:text="Selected · Subscription" />
48+
android:layout_weight="1"
49+
android:orientation="vertical"
50+
android:paddingStart="14dp"
51+
android:paddingTop="12dp"
52+
android:paddingEnd="8dp"
53+
android:paddingBottom="12dp">
6054

61-
<TextView
62-
android:id="@+id/tv_traffic"
63-
android:layout_width="match_parent"
64-
android:layout_height="wrap_content"
65-
android:layout_marginTop="2dp"
66-
android:textColor="?attr/colorOnSurfaceVariant"
67-
android:textSize="12sp"
68-
android:visibility="gone"
69-
tools:text="↑ 1.2 MiB ↓ 8.4 MiB"
70-
tools:visibility="visible" />
71-
</LinearLayout>
55+
<TextView
56+
android:id="@+id/tv_name"
57+
android:layout_width="match_parent"
58+
android:layout_height="wrap_content"
59+
android:ellipsize="end"
60+
android:maxLines="1"
61+
android:textColor="?attr/colorOnSurface"
62+
android:textSize="16sp"
63+
android:textStyle="bold"
64+
tools:text="My Subscription" />
7265

73-
<com.google.android.material.button.MaterialButton
74-
android:id="@+id/btn_share"
75-
style="@style/Widget.Material3.Button.IconButton"
76-
android:layout_width="wrap_content"
77-
android:layout_height="wrap_content"
78-
android:contentDescription="@string/action_share"
79-
app:icon="@drawable/ic_social_share"
80-
app:iconTint="?attr/colorPrimary" />
66+
<TextView
67+
android:id="@+id/tv_meta"
68+
android:layout_width="match_parent"
69+
android:layout_height="wrap_content"
70+
android:layout_marginTop="2dp"
71+
android:textColor="?attr/colorOnSurfaceVariant"
72+
android:textSize="12sp"
73+
tools:text="Selected · Subscription" />
8174

82-
<com.google.android.material.button.MaterialButton
83-
android:id="@+id/btn_update"
84-
style="@style/Widget.Material3.Button.TextButton"
85-
android:layout_width="wrap_content"
86-
android:layout_height="wrap_content"
87-
android:minWidth="0dp"
88-
android:text="@string/action_update" />
75+
<TextView
76+
android:id="@+id/tv_traffic"
77+
android:layout_width="match_parent"
78+
android:layout_height="wrap_content"
79+
android:layout_marginTop="2dp"
80+
android:textColor="?attr/colorOnSurfaceVariant"
81+
android:textSize="12sp"
82+
android:visibility="gone"
83+
tools:text="↑ 1.2 MiB ↓ 8.4 MiB"
84+
tools:visibility="visible" />
85+
</LinearLayout>
8986

90-
<com.google.android.material.button.MaterialButton
91-
android:id="@+id/btn_delete"
92-
style="@style/Widget.Material3.Button.TextButton"
93-
android:layout_width="wrap_content"
94-
android:layout_height="wrap_content"
95-
android:layout_marginEnd="4dp"
96-
android:minWidth="0dp"
97-
android:text="@string/action_delete"
98-
android:textColor="?attr/colorError" />
87+
<com.google.android.material.button.MaterialButton
88+
android:id="@+id/btn_update"
89+
style="@style/Widget.Material3.Button.TextButton"
90+
android:layout_width="wrap_content"
91+
android:layout_height="wrap_content"
92+
android:minWidth="0dp"
93+
android:text="@string/action_update" />
94+
95+
<com.google.android.material.button.MaterialButton
96+
android:id="@+id/btn_overflow"
97+
style="@style/Widget.Material3.Button.IconButton"
98+
android:layout_width="wrap_content"
99+
android:layout_height="wrap_content"
100+
android:layout_marginEnd="4dp"
101+
android:contentDescription="@string/action_more"
102+
app:icon="@drawable/ic_baseline_more_vert_24"
103+
app:iconTint="?attr/colorOnSurfaceVariant" />
104+
</LinearLayout>
99105
</LinearLayout>
100106
</com.google.android.material.card.MaterialCardView>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<menu xmlns:android="http://schemas.android.com/apk/res/android">
3+
<item
4+
android:id="@+id/action_select"
5+
android:title="@string/action_select" />
6+
<item
7+
android:id="@+id/action_share"
8+
android:title="@string/action_share" />
9+
<item
10+
android:id="@+id/action_delete"
11+
android:title="@string/action_delete" />
12+
</menu>

app/src/main/res/values-in/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
<string name="action_update">Perbarui</string>
3737
<string name="action_delete">Hapus</string>
3838
<string name="action_share">Bagikan</string>
39+
<string name="action_select">Pilih</string>
40+
<string name="action_more">Lainnya</string>
3941
<string name="badge_subscription">Langganan</string>
4042
<string name="badge_local">Lokal</string>
4143
<string name="badge_selected">Dipilih</string>

app/src/main/res/values-ru/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
<string name="action_update">Обновить</string>
3939
<string name="action_delete">Удалить</string>
4040
<string name="action_share">Поделиться</string>
41+
<string name="action_select">Выбрать</string>
42+
<string name="action_more">Ещё</string>
4143
<string name="badge_subscription">Подписка</string>
4244
<string name="badge_local">Локальный</string>
4345
<string name="badge_selected">Выбран</string>

app/src/main/res/values-zh-rCN/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
<string name="action_update">更新</string>
3737
<string name="action_delete">删除</string>
3838
<string name="action_share">分享</string>
39+
<string name="action_select">选用</string>
40+
<string name="action_more">更多</string>
3941
<string name="badge_subscription">订阅</string>
4042
<string name="badge_local">本地</string>
4143
<string name="badge_selected">已选择</string>

app/src/main/res/values-zh-rTW/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
<string name="action_update">更新</string>
3737
<string name="action_delete">刪除</string>
3838
<string name="action_share">分享</string>
39+
<string name="action_select">選用</string>
40+
<string name="action_more">更多</string>
3941
<string name="badge_subscription">訂閱</string>
4042
<string name="badge_local">本機</string>
4143
<string name="badge_selected">已選取</string>

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
<string name="action_update">Update</string>
4545
<string name="action_delete">Delete</string>
4646
<string name="action_share">Share</string>
47+
<string name="action_select">Select</string>
48+
<string name="action_more">More</string>
4749
<string name="profile_traffic" translatable="false">↑ %1$s ↓ %2$s</string>
4850
<string name="badge_subscription">Subscription</string>
4951
<string name="badge_local">Local</string>

0 commit comments

Comments
 (0)