Skip to content

Commit 9d6d44c

Browse files
committed
Modularize the manager UI into a shared library
Everything both this manager and a second host would draw -- the module list and rows, the store with its detail and install flow, the logs screen and reader, the hideable nav shell, the ambience header, and the appearance and language sheets -- moves into a new manager-ui library under org.matrix.vector.ui. The app keeps only what is its own and reaches the library through seam interfaces (LogSource, StoreInstallHost, AppearanceSettings, AmbienceSettings, LocaleController, FloatingNavSettings), so another manager can supply its own transport without forking the UI. The manager's split string files move to manager-ui/res, and crowdin.yml and the sync workflow follow. The live-log tail follows the reader's viewport rather than the loaded window, so scrolling up to read history pauses it; and ModuleRow gains an optional reachStart slot, a badge left of the reach band and off by default, for a host that wants a status on that line. The Japanese and Ukrainian translations from #911 are adapted to this layout, since that sync was raised against the old one.
1 parent fca40ed commit 9d6d44c

272 files changed

Lines changed: 7411 additions & 3057 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/crowdin.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ on:
99
# CLI's are defined independently — notably on whether `*` may match nothing — and a
1010
# trigger that quietly never fires is the worse failure.
1111
- manager/src/main/res/values/strings.xml
12-
- manager/src/main/res/values/strings_logs.xml
13-
- manager/src/main/res/values/strings_store.xml
12+
- manager-ui/src/main/res/values/strings_appearance.xml
13+
- manager-ui/src/main/res/values/strings_language.xml
14+
- manager-ui/src/main/res/values/strings_logs.xml
15+
- manager-ui/src/main/res/values/strings_modules.xml
16+
- manager-ui/src/main/res/values/strings_nav.xml
17+
- manager-ui/src/main/res/values/strings_store.xml
18+
- manager-ui/src/main/res/values/strings_trace.xml
1419
- daemon/src/main/res/values/strings.xml
1520

1621
jobs:

crowdin.yml

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,35 @@ files:
3232
'zh-CN': 'zh-rCN'
3333
'zh-HK': 'zh-rHK'
3434
'zh-TW': 'zh-rTW'
35-
- source: /manager/src/main/res/values/strings_logs.xml
36-
translation: /manager/src/main/res/values-%two_letters_code%/%original_file_name%
35+
# The shared manager UI library (manager-ui) owns the strings once split across the manager app;
36+
# they were lifted here when the UI was modularised so both the Vector manager and downstream
37+
# consumers (LSPatch) render the same, already-translated components.
38+
- source: /manager-ui/src/main/res/values/strings_appearance.xml
39+
translation: /manager-ui/src/main/res/values-%two_letters_code%/%original_file_name%
3740
type: android
3841
languages_mapping: *resource_folders
39-
- source: /manager/src/main/res/values/strings_store.xml
40-
translation: /manager/src/main/res/values-%two_letters_code%/%original_file_name%
42+
- source: /manager-ui/src/main/res/values/strings_language.xml
43+
translation: /manager-ui/src/main/res/values-%two_letters_code%/%original_file_name%
44+
type: android
45+
languages_mapping: *resource_folders
46+
- source: /manager-ui/src/main/res/values/strings_logs.xml
47+
translation: /manager-ui/src/main/res/values-%two_letters_code%/%original_file_name%
48+
type: android
49+
languages_mapping: *resource_folders
50+
- source: /manager-ui/src/main/res/values/strings_modules.xml
51+
translation: /manager-ui/src/main/res/values-%two_letters_code%/%original_file_name%
52+
type: android
53+
languages_mapping: *resource_folders
54+
- source: /manager-ui/src/main/res/values/strings_nav.xml
55+
translation: /manager-ui/src/main/res/values-%two_letters_code%/%original_file_name%
56+
type: android
57+
languages_mapping: *resource_folders
58+
- source: /manager-ui/src/main/res/values/strings_store.xml
59+
translation: /manager-ui/src/main/res/values-%two_letters_code%/%original_file_name%
60+
type: android
61+
languages_mapping: *resource_folders
62+
- source: /manager-ui/src/main/res/values/strings_trace.xml
63+
translation: /manager-ui/src/main/res/values-%two_letters_code%/%original_file_name%
4164
type: android
4265
languages_mapping: *resource_folders
4366
- source: /daemon/src/main/res/values/strings.xml

daemon/src/main/res/values-ja/strings.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
<string name="module_is_not_activated_yet_main_user_detailed">%1$s はインストールされましたが、 有効化されていません</string>
66
<string name="module_is_not_activated_yet_multi_user_detailed">%1$s はユーザー %2$s にインストールされましたが、 有効化されていません</string>
77
<string name="xposed_module_updated_notification_title">モジュールが更新されました</string>
8-
<string name="xposed_module_updated_notification_content">%s が更新されました。スコープ内のアプリを強制停止してから再起動してください</string>
8+
<string name="xposed_module_updated_notification_content">%s が更新されました。適用範囲内のアプリを強制停止してから再起動してください</string>
99
<string name="xposed_module_updated_notification_title_system">モジュールが更新されました。システムの再起動が必要です</string>
10-
<string name="xposed_module_updated_notification_content_system">%s が更新されました。スコープにシステムフレームワークが含まれているため、変更を適用するには再起動が必要です</string>
10+
<string name="xposed_module_updated_notification_content_system">%s が更新されました。適用範囲内にシステムフレームワークが含まれているため、変更を適用するには再起動が必要です</string>
1111
<string name="module_updated_channel_name">モジュールの更新完了通知</string>
1212
<string name="status_channel_name">Vector のステータス通知</string>
1313
<string name="vector_running_notification_title">Vector の読み込み完了通知</string>
1414
<string name="vector_running_notification_content">通知をタップしてマネージャーを開きます</string>
15-
<string name="xposed_module_request_scope_title">スコープのリクエスト</string>
16-
<string name="xposed_module_request_scope_content">ユーザー %2$s の %1$s が %3$s をそのスコープに追加するようリクエストしています。</string>
17-
<string name="scope_channel_name">スコープのリクエスト</string>
15+
<string name="xposed_module_request_scope_title">適用範囲のリクエスト</string>
16+
<string name="xposed_module_request_scope_content">ユーザー %2$s の %1$s が %3$s をその適用範囲に追加するようリクエストしています。</string>
17+
<string name="scope_channel_name">適用範囲のリクエスト</string>
1818
<string name="scope_approve">許可</string>
1919
<string name="scope_deny">拒否</string>
2020
<string name="never_ask_again">再度表示しない</string>

daemon/src/main/res/values-pl/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<string name="xposed_module_updated_notification_content_system">%s został zaktualizowany, ponieważ zakres zawiera systemowy framework, wymagany jest restart w celu zastosowania zmian</string>
1111
<string name="module_updated_channel_name">Aktualizacja modułu zakończona</string>
1212
<string name="status_channel_name">Stan Vector</string>
13-
<string name="vector_running_notification_title">Vector załadowany</string>
13+
<string name="vector_running_notification_title">Vector jest załadowany</string>
1414
<string name="vector_running_notification_content">Naciśnij powiadomienie, by włączyć menedżer</string>
1515
<string name="xposed_module_request_scope_title">Żądanie zakresu</string>
1616
<string name="xposed_module_request_scope_content">%1$s dla użytkownika %2$s żąda dodania %3$s do jego zakresu.</string>

manager-ui/build.gradle.kts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Shared manager UI, consumed by both Vector's manager app and LSPatch's manager (via the
2+
// composite-build substitution vector:manager-ui). Holds the reusable, backend-agnostic Compose:
3+
// the panel header, search field, ambience, theme seed, and the store HTML renderer.
4+
plugins {
5+
alias(libs.plugins.agp.lib)
6+
alias(libs.plugins.kotlin.compose)
7+
}
8+
9+
kotlin {
10+
compilerOptions {
11+
optIn.addAll(
12+
"androidx.compose.material3.ExperimentalMaterial3Api",
13+
"androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
14+
"androidx.compose.foundation.layout.ExperimentalLayoutApi",
15+
)
16+
}
17+
}
18+
19+
android {
20+
namespace = "org.matrix.vector.ui"
21+
buildFeatures { compose = true }
22+
}
23+
24+
dependencies {
25+
implementation(libs.gson)
26+
implementation(libs.androidx.core.ktx)
27+
implementation(platform(libs.androidx.compose.bom))
28+
implementation(libs.androidx.compose.ui)
29+
implementation(libs.androidx.compose.ui.graphics)
30+
implementation(libs.androidx.compose.material3)
31+
implementation(libs.androidx.compose.material3.adaptive.navigation.suite)
32+
implementation(libs.androidx.compose.material.icons.extended)
33+
implementation(libs.androidx.lifecycle.viewmodel.compose)
34+
implementation(libs.androidx.activity.compose)
35+
implementation(libs.androidx.webkit)
36+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package org.matrix.vector.ui
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.Arrangement
6+
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.ColumnScope
9+
import androidx.compose.foundation.layout.Row
10+
import androidx.compose.foundation.layout.Spacer
11+
import androidx.compose.foundation.layout.fillMaxSize
12+
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.foundation.layout.height
14+
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.layout.size
16+
import androidx.compose.foundation.layout.width
17+
import androidx.compose.foundation.shape.CircleShape
18+
import androidx.compose.material3.Icon
19+
import androidx.compose.material3.MaterialTheme
20+
import androidx.compose.material3.Text
21+
import androidx.compose.runtime.Composable
22+
import androidx.compose.ui.Alignment
23+
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.draw.clip
25+
import androidx.compose.ui.graphics.Color
26+
import androidx.compose.ui.graphics.vector.ImageVector
27+
import androidx.compose.ui.text.font.FontFamily
28+
import androidx.compose.ui.text.style.TextAlign
29+
import androidx.compose.ui.text.style.TextOverflow
30+
import androidx.compose.ui.unit.dp
31+
32+
/**
33+
* The header at the top of a package action drawer: the package's icon, its label, its package
34+
* name, and an optional slot for extra facts (a description, an API level).
35+
*
36+
* Mirrors the row it was opened from, so the reader can see which package the actions below act on.
37+
* The icon is a slot rather than a bitmap so each host draws it however it already draws app icons.
38+
*/
39+
@Composable
40+
fun ActionDrawerHeader(
41+
label: String,
42+
packageName: String,
43+
modifier: Modifier = Modifier,
44+
icon: (@Composable () -> Unit)? = null,
45+
extraContent: (@Composable ColumnScope.() -> Unit)? = null,
46+
) {
47+
Row(
48+
modifier = modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, bottom = 12.dp),
49+
verticalAlignment = Alignment.CenterVertically,
50+
horizontalArrangement = Arrangement.spacedBy(16.dp),
51+
) {
52+
icon?.invoke()
53+
Column(Modifier.weight(1f)) {
54+
Text(
55+
text = label,
56+
style = MaterialTheme.typography.titleMedium,
57+
maxLines = 1,
58+
overflow = TextOverflow.Ellipsis,
59+
)
60+
Text(
61+
text = packageName,
62+
fontFamily = FontFamily.Monospace,
63+
style = MaterialTheme.typography.bodySmall,
64+
color = MaterialTheme.colorScheme.onSurfaceVariant,
65+
maxLines = 1,
66+
overflow = TextOverflow.Ellipsis,
67+
)
68+
extraContent?.invoke(this)
69+
}
70+
}
71+
}
72+
73+
/**
74+
* One full-width action inside a package action drawer: its glyph in a tinted disc, the action's
75+
* title, and — when it has one — the line under it saying what the action does.
76+
*
77+
* The disc is what lets a destructive action look destructive: an error-red glyph on a bare row is
78+
* easy to miss, the same glyph on a red disc is not. Once one row carries it they all have to, or
79+
* the bare one reads as a different kind of thing sitting in the same list. The measurements keep a
80+
* single column running down the whole drawer: 24dp of margin, a 40dp disc and 20dp of gap put
81+
* every title where [ActionDrawerHeader] puts the package's name.
82+
*
83+
* [tint] colours a destructive or emphasised action; a title inherits it only when it is the error
84+
* colour, so a merely emphasised row keeps a readable heading. [trailing] is for a row that carries
85+
* state as well as an action — a switch, a badge. [onClick] is nullable because a row can be a
86+
* statement rather than an action ("not in the store"), and a caller that needs a non-button role
87+
* (a toggle announced as a switch) supplies its own behaviour through [modifier] and leaves this
88+
* null.
89+
*/
90+
@Composable
91+
fun ActionDrawerItem(
92+
icon: ImageVector,
93+
title: String,
94+
subtitle: String? = null,
95+
modifier: Modifier = Modifier,
96+
tint: Color? = null,
97+
trailing: (@Composable () -> Unit)? = null,
98+
onClick: (() -> Unit)? = null,
99+
) {
100+
val colors = MaterialTheme.colorScheme
101+
val accent = tint ?: colors.onSurfaceVariant
102+
Row(
103+
modifier =
104+
Modifier.fillMaxWidth()
105+
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier)
106+
.then(modifier)
107+
.padding(horizontal = 24.dp, vertical = 12.dp),
108+
verticalAlignment = Alignment.CenterVertically,
109+
) {
110+
Box(
111+
modifier =
112+
Modifier.size(40.dp).clip(CircleShape).background(accent.copy(alpha = 0.12f)),
113+
contentAlignment = Alignment.Center,
114+
) {
115+
Icon(icon, contentDescription = null, tint = accent, modifier = Modifier.size(22.dp))
116+
}
117+
Spacer(Modifier.width(20.dp))
118+
Column(Modifier.weight(1f)) {
119+
Text(
120+
text = title,
121+
style = MaterialTheme.typography.bodyLarge,
122+
color = if (tint == colors.error) colors.error else colors.onSurface,
123+
)
124+
if (subtitle != null) {
125+
Text(
126+
text = subtitle,
127+
style = MaterialTheme.typography.bodySmall,
128+
color = colors.onSurfaceVariant,
129+
)
130+
}
131+
}
132+
if (trailing != null) {
133+
Spacer(Modifier.width(12.dp))
134+
trailing()
135+
}
136+
}
137+
}
138+
139+
/**
140+
* The centred icon-and-line a list panel shows when it has nothing to list — a still-loading set, a
141+
* genuinely empty one, or a search that matched nothing. The same shape on every panel, so an empty
142+
* screen always reads as an empty screen rather than as something broken.
143+
*/
144+
@Composable
145+
fun PanelEmptyState(icon: ImageVector, text: String, modifier: Modifier = Modifier) {
146+
Box(modifier.fillMaxSize().padding(32.dp), contentAlignment = Alignment.Center) {
147+
Column(horizontalAlignment = Alignment.CenterHorizontally) {
148+
Icon(
149+
icon,
150+
contentDescription = null,
151+
modifier = Modifier.size(40.dp),
152+
tint = MaterialTheme.colorScheme.outline,
153+
)
154+
Spacer(Modifier.height(12.dp))
155+
Text(
156+
text = text,
157+
color = MaterialTheme.colorScheme.onSurfaceVariant,
158+
textAlign = TextAlign.Center,
159+
)
160+
}
161+
}
162+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.matrix.vector.ui
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.material3.MaterialTheme
6+
import androidx.compose.material3.Text
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.ui.Alignment
9+
import androidx.compose.ui.text.font.FontWeight
10+
import androidx.compose.ui.unit.dp
11+
import androidx.compose.ui.unit.sp
12+
13+
/**
14+
* `API 101` / `Xposed 93`, with the scale name small and quiet and the number carrying the colour.
15+
*
16+
* The scale name is context that rarely changes and repeats down every row; the number is the fact
17+
* being checked, so it is the only part given weight and colour. A caller with no value to show
18+
* passes `"?"` for [value] and `incompatible = true`, which keeps the badge the same shape while the
19+
* missing number reads as missing rather than as a different kind of thing.
20+
*
21+
* Model-agnostic on purpose: Vector maps a module's declared API into ([label], [value]); LSPatch
22+
* passes its own text. Both get one badge instead of two that drift apart.
23+
*/
24+
@Composable
25+
fun ApiBadge(label: String, value: String, incompatible: Boolean = false) {
26+
val colors = MaterialTheme.colorScheme
27+
Row(verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.spacedBy(3.dp)) {
28+
Text(
29+
text = label,
30+
style = MaterialTheme.typography.labelSmall.copy(fontSize = 8.sp),
31+
color = colors.onSurfaceVariant.copy(alpha = 0.7f),
32+
)
33+
Text(
34+
text = value,
35+
style = MaterialTheme.typography.labelMedium,
36+
fontWeight = FontWeight.SemiBold,
37+
color = if (incompatible) colors.error else colors.primary,
38+
)
39+
}
40+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.matrix.vector.ui
2+
3+
import android.content.ClipData
4+
import android.content.ClipboardManager
5+
import android.content.Context
6+
7+
/**
8+
* Puts text on the clipboard, or does nothing.
9+
*
10+
* Shared by the reusable screens — every one of them copies for the same reason, the text is on its
11+
* way into a bug report, so they want the same silence when there is no clipboard service to hand.
12+
* There may not be: a host may be running inside `com.android.shell`, and a failure to copy is not
13+
* worth a crash on a screen someone opened *because* something had already gone wrong.
14+
*/
15+
fun copyToClipboard(context: Context, text: String) {
16+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
17+
clipboard?.setPrimaryClip(ClipData.newPlainText(context.packageName, text))
18+
}

manager/src/main/kotlin/org/matrix/vector/manager/ui/components/ColorWheel.kt renamed to manager-ui/src/main/kotlin/org/matrix/vector/ui/ColorWheel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.matrix.vector.manager.ui.components
1+
package org.matrix.vector.ui
22

33
import android.graphics.Bitmap
44
import androidx.compose.foundation.Canvas
@@ -31,7 +31,7 @@ import kotlin.math.hypot
3131
import kotlin.math.roundToInt
3232
import kotlinx.coroutines.Dispatchers
3333
import kotlinx.coroutines.withContext
34-
import org.matrix.vector.manager.ui.theme.SeedScheme
34+
import org.matrix.vector.ui.theme.SeedScheme
3535

3636
/** The chroma the rim of the wheel represents. Past this, almost nothing is in gamut anyway. */
3737
private const val MAX_CHROMA = 110f

0 commit comments

Comments
 (0)