From 2936d77588b97bad38bb897298684ba5cc6db7ac Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Wed, 26 Aug 2026 22:34:08 +0800 Subject: [PATCH 01/26] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/core.yml | 129 +++------------------------------- .github/workflows/crowdin.yml | 38 ---------- build.gradle.kts | 17 +++-- 3 files changed, 22 insertions(+), 162 deletions(-) delete mode 100644 .github/workflows/crowdin.yml diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 89b65713d..7d185e670 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -27,6 +27,7 @@ jobs: # branch came from a fork, and the checked-out HEAD is an ephemeral merge commit that exists # nowhere and cannot be looked up by anyone who reads it off a device. Both head.* values are # null outside a pull request, where the defaults are already right. + VECTOR_BASE_REMOTE: upstream VECTOR_BUILD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name || github.repository }} VECTOR_BUILD_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }} CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion" @@ -40,6 +41,12 @@ jobs: with: submodules: recursive fetch-depth: 0 + + - name: Fetch upstream JingMatrix Vector (only master + tags) + run: | + git remote add upstream https://github.com/JingMatrix/Vector.git + git fetch upstream master:refs/remotes/upstream/master --depth=0 + git fetch upstream --tags --no-recurse-submodules # Before anything is built, because it needs nothing but Python and a translator's mistake # should not wait twenty minutes to surface. Crowdin owns the content of these files, so what @@ -57,25 +64,19 @@ jobs: # to it and cannot be given one, so a fork builds unsigned, as does any branch that is not # master: neither publishes anything. - name: Write key - if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/master' ) || github.ref_type == 'tag' }} + # pull_request事件不注入密钥,其余(workflow_dispatch / push master / tag)都注入 + if: ${{ github.event_name != 'pull_request' }} env: KEY_STORE: ${{ secrets.KEY_STORE }} KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }} ALIAS: ${{ secrets.ALIAS }} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} - IS_UPSTREAM: ${{ github.repository_owner == 'JingMatrix' }} run: | set -euo pipefail if [ -z "$KEY_STORE" ]; then - if [ "$IS_UPSTREAM" = "true" ]; then - echo "::error::KEY_STORE is empty on ${{ github.ref }}. Refusing to publish an unsigned build." - exit 1 - fi - echo "No signing secret on this fork; building unsigned." + echo "::warning::No signing secret, building unsigned debug build." exit 0 fi - # Through the environment rather than interpolated into the script: a password holding a - # quote would otherwise be pasted into a shell word and mangled, or worse, executed. { echo "androidStorePassword=$KEY_STORE_PASSWORD" echo "androidKeyAlias=$ALIAS" @@ -169,112 +170,4 @@ jobs: path: | zygisk/build/symbols daemon/build/symbols - dex2oat/build/symbols - - # --- stable release -------------------------------------------------------------------- - # - # A pushed `v*` tag is a stable release, not a canary. It is published here from the same - # signed zips this build produced, so cutting a release is `git tag && git push` with nothing - # hand-uploaded afterwards — the hand-assembly is exactly how v2.0's assets came to disagree - # with zygisk/update.json (#811), and an automated attach cannot drift from what was built. - # - # Marked `--latest`, unlike the canaries below: this is what `releases/latest`, and therefore - # the manager's update check, resolves to. The body is the module's own zygisk/changelog.md - # under a GitHub H1 the release page shows. The changelog itself carries no title line — the - # manager renders it beneath its own heading, so a banner there would only read twice. - - name: Publish release - if: ${{ success() && github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} - run: | - set -euo pipefail - # `%s` arguments, never the format string, so a `%` anywhere in the changelog is literal. - notes=$(printf '# 🎉 Vector %s 🎉\n\n%s\n' "${TAG#v}" "$(cat zygisk/changelog.md)") - - # Recreated rather than edited so a re-run replaces the assets instead of appending a - # second copy. No `--cleanup-tag`: the release is rebuilt, the pushed release tag is kept. - gh release delete "$TAG" --yes 2>/dev/null || true - gh release create "$TAG" \ - --latest \ - --title "Vector ${TAG}" \ - --notes "$notes" \ - zygisk/release/Vector-v*-Release.zip \ - zygisk/release/Vector-v*-Debug.zip - - # --- canary distribution --------------------------------------------------------------- - # - # The zips above are also attached to a prerelease, because an Actions artifact cannot be - # downloaded without a GitHub account — `GET /actions/artifacts//zip` answers 401 to an - # anonymous caller, while a release asset answers 206. Testing a canary is the lowest-friction - # way for an ordinary user to help, so it must not require handing an OAuth grant to anyone, - # and it must work for the many users who cannot reach GitHub's login page at all. - # - # Marked prerelease so `releases/latest` — which is what update checks read — keeps pointing - # at the last stable tag. - - name: Publish canary prerelease - if: >- - success() && - (github.event_name == 'workflow_dispatch' || - (github.event_name == 'push' && github.ref == 'refs/heads/master')) - env: - GH_TOKEN: ${{ github.token }} - VERSION_CODE: ${{ steps.prepareArtifact.outputs.versionCode }} - VERSION_NAME: ${{ steps.prepareArtifact.outputs.versionName }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} - run: | - set -euo pipefail - tag="canary-${VERSION_CODE}" - subject=$(git log -1 --pretty=%s) - short=$(git rev-parse --short HEAD) - - notes=$(cat </dev/null || true - gh release create "$tag" \ - --prerelease \ - --target "${{ github.sha }}" \ - --title "Vector ${VERSION_NAME} canary ${VERSION_CODE}" \ - --notes "$notes" \ - zygisk/release/Vector-v*-Release.zip \ - zygisk/release/Vector-v*-Debug.zip - - # Five is what a tester needs: enough to bisect a regression across a few days, few enough - # that the releases page is still mostly releases. Sorted by version code, which is the commit - # count and therefore monotonic, rather than by date, which reruns and reverts can disorder. - - name: Keep only the five most recent canaries - if: >- - success() && - (github.event_name == 'workflow_dispatch' || - (github.event_name == 'push' && github.ref == 'refs/heads/master')) - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - # `grep` exits 1 when nothing matches, which under `pipefail` would fail the step on a - # repository that has no canaries yet. Collected first, then acted on. - tags=$(gh release list --limit 100 --json tagName --jq '.[].tagName' | grep '^canary-' || true) - [ -n "$tags" ] || exit 0 - echo "$tags" | sort -t- -k2 -n -r | tail -n +6 | while read -r old; do - echo "Removing $old" - gh release delete "$old" --yes --cleanup-tag - done + dex2oat/build/symbols \ No newline at end of file diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml deleted file mode 100644 index ccaf1179c..000000000 --- a/.github/workflows/crowdin.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Crowdin Action - -on: - workflow_dispatch: - push: - branches: [ master ] - paths: - # Named rather than globbed, to match crowdin.yml. The trigger's matching rules and the - # CLI's are defined independently — notably on whether `*` may match nothing — and a - # trigger that quietly never fires is the worse failure. - - manager/src/main/res/values/strings.xml - - manager-ui/src/main/res/values/strings_appearance.xml - - manager-ui/src/main/res/values/strings_language.xml - - manager-ui/src/main/res/values/strings_logs.xml - - manager-ui/src/main/res/values/strings_modules.xml - - manager-ui/src/main/res/values/strings_nav.xml - - manager-ui/src/main/res/values/strings_store.xml - - manager-ui/src/main/res/values/strings_trace.xml - - daemon/src/main/res/values/strings.xml - -jobs: - synchronize-with-crowdin: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: crowdin action - uses: crowdin/github-action@v2 - with: - upload_translations: true - download_translations: false - upload_sources: true - config: 'crowdin.yml' - crowdin_branch_name: master - env: - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }} diff --git a/build.gradle.kts b/build.gradle.kts index f8c9c30f6..50301f53d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,17 +21,19 @@ plugins { } /** A ValueSource that executes 'git rev-list --count' to get the total commit count. */ -abstract class GitCommitCountValueSource : ValueSource { +abstract class GitCommitCountValueSource : ValueSource { + interface Parameters : ValueSourceParameters { + val baseRemoteName: Property + } @get:Inject abstract val execOperations: ExecOperations - override fun obtain(): String { + val remoteName = parameters.baseRemoteName.get() val output = ByteArrayOutputStream() val result = execOperations.exec { - commandLine("git", "rev-list", "--count", "refs/remotes/origin/master") + commandLine("git", "rev-list", "--count", "refs/remotes/$remoteName/master") standardOutput = output isIgnoreExitValue = true } - // Return the count if successful, otherwise a default of "1". return if (result.exitValue == 0 && output.toString().isNotBlank()) { output.toString().trim() } else { @@ -39,7 +41,6 @@ abstract class GitCommitCountValueSource : ValueSource { @get:Inject abstract val execOperations: ExecOperations @@ -202,7 +203,11 @@ abstract class GitCommitHashValueSource : ValueSource Date: Wed, 26 Aug 2026 22:40:31 +0800 Subject: [PATCH 02/26] Update core.yml --- .github/workflows/core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 7d185e670..80bcb4040 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -45,7 +45,7 @@ jobs: - name: Fetch upstream JingMatrix Vector (only master + tags) run: | git remote add upstream https://github.com/JingMatrix/Vector.git - git fetch upstream master:refs/remotes/upstream/master --depth=0 + git fetch upstream master git fetch upstream --tags --no-recurse-submodules # Before anything is built, because it needs nothing but Python and a translator's mistake From 65f400f76ca31d6bc7b275eebe4efd32d73636a1 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Wed, 26 Aug 2026 23:45:23 +0800 Subject: [PATCH 03/26] =?UTF-8?q?=E9=BB=91=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../res/drawable/ic_statue_monochrome.xml | 17 +- .../main/res/drawable/ic_winged_victory.xml | 478 +----------------- .../res/mipmap-anydpi-v26/ic_launcher.xml | 4 +- 3 files changed, 37 insertions(+), 462 deletions(-) diff --git a/daemon/src/main/res/drawable/ic_statue_monochrome.xml b/daemon/src/main/res/drawable/ic_statue_monochrome.xml index 7144f80d9..1bf39c763 100644 --- a/daemon/src/main/res/drawable/ic_statue_monochrome.xml +++ b/daemon/src/main/res/drawable/ic_statue_monochrome.xml @@ -4,15 +4,16 @@ android:height="24dp" android:viewportWidth="350" android:viewportHeight="350"> - - + + android:pathData="M110,112 L175,258 L240,112 L198,112 L175,172 L152,112 Z + M134,138 L148,162 L148,192 M216,138 L202,162 L202,192 + M134,138 m -5,0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0 + M148,162 m -5,0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0 + M148,192 m -5,0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0 + M216,138 m -5,0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0 + M202,162 m -5,0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0 + M202,192 m -5,0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0" /> diff --git a/manager/src/main/res/drawable/ic_winged_victory.xml b/manager/src/main/res/drawable/ic_winged_victory.xml index 193431ad9..3a2ba6369 100644 --- a/manager/src/main/res/drawable/ic_winged_victory.xml +++ b/manager/src/main/res/drawable/ic_winged_victory.xml @@ -5,460 +5,34 @@ android:viewportWidth="353" android:viewportHeight="353"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:pathData="M111,113 L176.5,259 L242,113 L199,113 L176.5,174 L154,113 Z" + android:fillColor="#22F0F8" /> + + + android:pathData="M126,126 L176.5,244 L227,126 L204,126 L176.5,184 L149,126 Z" + android:fillColor="#14D8B0" /> + + + android:pathData="M139,140 L176.5,229 L214,140 L194,140 L176.5,194 L159,140 Z" + android:fillColor="#08A8A8" /> + + + android:pathData="M136,139 L151,164 L151,194 M217,139 L202,164 L202,194" + android:strokeColor="#068080" + android:strokeWidth="2.5" + android:fillColor="#00000000" /> + + + + + + + + + diff --git a/manager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/manager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 935333819..ac44568e4 100644 --- a/manager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/manager/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -4,11 +4,11 @@ + android:inset="22%" /> + android:inset="22%" /> From df0125f1e49f9235c60f2c3fc3169be58cc3144d Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Wed, 26 Aug 2026 23:52:42 +0800 Subject: [PATCH 04/26] Update ic_winged_victory.xml --- .../src/main/res/drawable/ic_winged_victory.xml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/manager/src/main/res/drawable/ic_winged_victory.xml b/manager/src/main/res/drawable/ic_winged_victory.xml index 3a2ba6369..b0d59306b 100644 --- a/manager/src/main/res/drawable/ic_winged_victory.xml +++ b/manager/src/main/res/drawable/ic_winged_victory.xml @@ -27,12 +27,14 @@ android:strokeWidth="2.5" android:fillColor="#00000000" /> - - - - - - - + + + + + + + + + From 9ad35c38816613ea7e0bd4e5bd3e3f5c2ea5b24f Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 00:04:06 +0800 Subject: [PATCH 05/26] Update ic_winged_victory.xml --- manager/src/main/res/drawable/ic_winged_victory.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manager/src/main/res/drawable/ic_winged_victory.xml b/manager/src/main/res/drawable/ic_winged_victory.xml index b0d59306b..18a510270 100644 --- a/manager/src/main/res/drawable/ic_winged_victory.xml +++ b/manager/src/main/res/drawable/ic_winged_victory.xml @@ -27,7 +27,7 @@ android:strokeWidth="2.5" android:fillColor="#00000000" /> - + From 04f0639c55dbbe22b35bc141f5e4143b89dcbedb Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 00:06:24 +0800 Subject: [PATCH 06/26] Update ic_winged_victory.xml --- .../main/res/drawable/ic_winged_victory.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/manager/src/main/res/drawable/ic_winged_victory.xml b/manager/src/main/res/drawable/ic_winged_victory.xml index 18a510270..0e764fd24 100644 --- a/manager/src/main/res/drawable/ic_winged_victory.xml +++ b/manager/src/main/res/drawable/ic_winged_victory.xml @@ -27,14 +27,15 @@ android:strokeWidth="2.5" android:fillColor="#00000000" /> - - - - - - - - - + + From aa44aacc07c6040db87df0a89fcda9b979284bc3 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 01:58:11 +0800 Subject: [PATCH 07/26] =?UTF-8?q?=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../res/drawable/ic_statue_monochrome.xml | 39 ++++++++----- .../main/res/drawable/ic_winged_victory.xml | 55 +++++++------------ 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/daemon/src/main/res/drawable/ic_statue_monochrome.xml b/daemon/src/main/res/drawable/ic_statue_monochrome.xml index 1bf39c763..890989999 100644 --- a/daemon/src/main/res/drawable/ic_statue_monochrome.xml +++ b/daemon/src/main/res/drawable/ic_statue_monochrome.xml @@ -1,19 +1,28 @@ - + android:height="512dp" + android:width="512dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + android:fillColor="#FFFFFFFF" + android:pathData="M39.4,23l-0.8,-4l-12.6,2.6l0,-13.6l-4,0l0,12.3l-13.9,-9l-2.2,3.4l15.2,9.8l-11.7,15.3l3.2,2.4l11.3,-14.8l8.4,12.7l3.4,-2.2l-8.4,-12.5z" /> + + + + + + diff --git a/manager/src/main/res/drawable/ic_winged_victory.xml b/manager/src/main/res/drawable/ic_winged_victory.xml index 0e764fd24..ecec36271 100644 --- a/manager/src/main/res/drawable/ic_winged_victory.xml +++ b/manager/src/main/res/drawable/ic_winged_victory.xml @@ -1,41 +1,28 @@ - - + android:height="512dp" + android:width="512dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> - - + android:fillColor="#FFCFD8DC" + android:pathData="M39.4,23l-0.8,-4l-12.6,2.6l0,-13.6l-4,0l0,12.3l-13.9,-9l-2.2,3.4l15.2,9.8l-11.7,15.3l3.2,2.4l11.3,-14.8l8.4,12.7l3.4,-2.2l-8.4,-12.5z" /> - - + android:fillColor="#FF3F51B5" + android:pathData="M24,24m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0" /> - - + android:fillColor="#FF00BCD4" + android:pathData="M24,8m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0" /> - - - - + android:fillColor="#FF00BCD4" + android:pathData="M39,21m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0" /> + + + From ecbac5ebb9da6bb29d0ea10df140a98d97efce73 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 15:25:13 +0800 Subject: [PATCH 08/26] Add files via upload --- .../kotlin/org/matrix/vector/manager/ui/MainActivity.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/MainActivity.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/MainActivity.kt index 94cb56565..9574aa6af 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/MainActivity.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/MainActivity.kt @@ -6,11 +6,9 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.CompositionLocalProvider -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import org.matrix.vector.manager.data.repository.LaunchShortcut import org.matrix.vector.manager.di.ServiceLocator import org.matrix.vector.manager.ui.navigation.DeepLink -import org.matrix.vector.manager.ui.screens.splash.SplashGate import org.matrix.vector.manager.ui.theme.LocalizedContent import org.matrix.vector.manager.ui.theme.LocalizedOverlay import org.matrix.vector.manager.ui.theme.VectorTheme @@ -27,9 +25,6 @@ import org.matrix.vector.ui.LocalDialogLocalizer class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { - // Must precede super.onCreate. Handing off from the platform splash is what keeps an - // unthemed frame from appearing between the system splash and the Compose one. - val splash = installSplashScreen() enableEdgeToEdge() super.onCreate(savedInstanceState) @@ -52,8 +47,6 @@ class MainActivity : ComponentActivity() { // Keep the platform splash up only until the first frame is ready to draw; the Compose // splash then plays and decides for itself when the daemon has been given long enough. - splash.setKeepOnScreenCondition { false } - // The launch intent can name where to open — the module a notification was about. // // Offered on every creation, including a restored one. Parasitically the zygisk hooker @@ -79,7 +72,7 @@ class MainActivity : ComponentActivity() { CompositionLocalProvider( LocalDialogLocalizer provides { content -> LocalizedOverlay(content) } ) { - VectorTheme { SplashGate { VectorApp() } } + VectorTheme { VectorApp() } } } } From de91230ecddfbb96a5a750b020ea9082c2dcdd93 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 15:26:08 +0800 Subject: [PATCH 09/26] Add files via upload --- .../manager/ui/screens/splash/SplashScreen.kt | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/splash/SplashScreen.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/splash/SplashScreen.kt index 88450db33..b4e32377a 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/splash/SplashScreen.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/splash/SplashScreen.kt @@ -1,6 +1,5 @@ package org.matrix.vector.manager.ui.screens.splash -import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background @@ -23,18 +22,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics -import kotlinx.coroutines.delay -import kotlinx.coroutines.withTimeoutOrNull import org.matrix.vector.manager.R -import org.matrix.vector.manager.di.ServiceLocator -import kotlinx.coroutines.flow.first -import org.matrix.vector.manager.logW /** How long the animation itself needs, so the statue is never cut off mid-fade. */ -private const val ANIMATION_MS = 800L - -/** The longest we wait on the daemon before showing the UI anyway, in its "not activated" state. */ -private const val DAEMON_TIMEOUT_MS = 2_500L +private const val ANIMATION_MS = 700L /** * The Winged Victory, fading and scaling in — Vector, from *Victoria*. @@ -46,25 +37,6 @@ private const val DAEMON_TIMEOUT_MS = 2_500L * [ANIMATION_MS] is then spent after the handshake rather than overlapped with it, so a binder that * resolves instantly still leaves the fade its full length instead of a flash of half-drawn artwork. */ -@Composable -fun SplashGate(content: @Composable () -> Unit) { - var ready by remember { mutableStateOf(false) } - - LaunchedEffect(Unit) { - // The handshake first, under a ceiling; then the artwork's own duration. - val bound = - withTimeoutOrNull(DAEMON_TIMEOUT_MS) { ServiceLocator.service.first { it != null } } - if (bound == null) { - logW("splash: no daemon binder after ${DAEMON_TIMEOUT_MS}ms, continuing unactivated") - } - delay(ANIMATION_MS) - ready = true - } - - Crossfade(targetState = ready, animationSpec = tween(320), label = "splashHandoff") { done -> - if (done) content() else WingedVictory() - } -} /** The splash artwork, also summoned by the header's easter egg. */ @Composable @@ -101,7 +73,7 @@ fun WingedVictory() { // The drawable is a 108dp square whose figure runs nearly the full height of its // viewport, so it is given 92% of both dimensions and fitted inside — which keeps its // proportions in portrait and landscape without stretching or clipping. - modifier = Modifier.fillMaxSize(0.92f).scale(scale).alpha(alpha), + modifier = Modifier.fillMaxSize(0.82f).scale(scale).alpha(alpha), ) } } From 9878e8e9d936fca1f4fbe9dacc4db80bc5b52e54 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 15:28:20 +0800 Subject: [PATCH 10/26] Add files via upload --- .../manager/ui/screens/home/HomeViewModel.kt | 78 ++++++------------- 1 file changed, 24 insertions(+), 54 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index 725ff534d..5a126eb20 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -326,58 +326,39 @@ class HomeViewModel( init { refreshPresence() - // The binder may arrive after this ViewModel exists — injection order is not ours to - // control — so status is re-derived whenever it changes rather than read once in init. + + // ==================== 秒开 + 后台自动更新 ==================== + // 1. 立即从缓存加载(瞬间返回,不等待网络) + _feed.value = github.load(GitHubRepository.Freshness.Cached) + + // 2. 后台异步刷新,不阻塞 UI viewModelScope.launch { - // Both flows, because a refused binder leaves `service` null and only moves - // `peerDescriptor`. Collecting `service` alone would see no change at all — it was - // already null — and the header would sit on "not activated" for a framework that is - // running and simply out of step with this build. - combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } - .collect { service -> - refreshStatus(service) - // Both switches on the status page hold the daemon's state rather than ours, - // and the binder is what they need. This runs for every binder, including one - // already in hand when `refreshPresence` ran above — a second read of two - // idempotent values — and it is here for the one that arrives afterwards, - // which that call found nothing to ask about and returned. Nor is it the last - // such moment: `refreshPresence` asks again whenever a screen that shows them - // is opened, since this flow does not emit a second time while one binder - // stays alive. - if (service != null) refreshToggles() - } + delay(300) // 让首页先渲染完成 + val fresh = github.load(GitHubRepository.Freshness.Revalidate) + _feed.value = fresh + _refreshing.value = false + _windowChanged.value = false } - // Returning to Home is not a reason to talk to GitHub. The page renders from disk every - // time and only occasionally goes and checks — the window it shows changes a few times a - // week at most, and the user's battery and their share of an anonymous rate limit are worth - // more than redrawing identical rows. Pull-to-refresh is always there when they do want it. - // - // Opening the app *is* a reason, and the toss used to apply there too: four launches in five - // showed whatever was on disk, which after a while is a feed that has quietly stopped - // moving — and a cold start is exactly when it has had the longest to go stale. So the first - // Home of a process always checks, and the toss governs only the visits after it. Process - // scope rather than a timestamp because that is what "since the app was opened" means here: - // parasitically the host is `com.android.shell` and is killed constantly, but each of those - // deaths is also what makes the next arrival a first launch to the reader. - val firstThisProcess = !homeOpenedThisProcess - homeOpenedThisProcess = true - val checkNow = firstThisProcess || Random.nextFloat() >= FEED_PAUSE_PROBABILITY - refreshFeed( - if (checkNow) GitHubRepository.Freshness.Revalidate - else GitHubRepository.Freshness.Cached - ) + // ============================================================ + + // 3. 监听窗口变化(用户调整时间范围) viewModelScope.launch { - // drop(1): the value on subscription is the window already rendered. ServiceLocator.settings.activityWindowMonths.drop(1).collect { _windowChanged.value = true - // From disk, not from GitHub. The archive already holds the commits; the window is - // only a view of it, so re-cutting it costs nothing and the feed answers on the - // frame after the sheet closes. _feed.value = github.load(GitHubRepository.Freshness.Cached) } } + + // 4. 监听框架状态变化 + viewModelScope.launch { + combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } + .collect { service -> + refreshStatus(service) + if (service != null) refreshToggles() + } + } } - + private suspend fun refreshStatus(service: IManagerService?) { if (service == null || !daemon.isAlive) { // A binder did arrive and was refused for speaking a different generation of the @@ -677,17 +658,6 @@ class HomeViewModel( val historyStalled: StateFlow = _exhausted.asStateFlow() companion object { - /** How often *returning* to Home leaves the feed on what is already on disk. */ - private const val FEED_PAUSE_PROBABILITY = 0.8f - - /** - * True once Home has been built in this process, whatever the feed did about it. - * - * On the companion because that is exactly the scope wanted — one per process, shared by - * every HomeViewModel a session builds, and gone when the host is killed. Volatile because - * `init` runs on whichever thread built the ViewModel. - */ - @Volatile private var homeOpenedThisProcess = false /** * How many times a day the badge has to be used before it stops explaining itself. From c4fdf825417203f2054e39c48b52e65f0adc849e Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 15:50:18 +0800 Subject: [PATCH 11/26] Add files via upload --- .../manager/ui/screens/home/HomeViewModel.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index 5a126eb20..ac0055384 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.flowOn @@ -327,21 +328,17 @@ class HomeViewModel( init { refreshPresence() - // ==================== 秒开 + 后台自动更新 ==================== - // 1. 立即从缓存加载(瞬间返回,不等待网络) - _feed.value = github.load(GitHubRepository.Freshness.Cached) - - // 2. 后台异步刷新,不阻塞 UI viewModelScope.launch { - delay(300) // 让首页先渲染完成 + // 立即显示缓存 + _feed.value = github.load(GitHubRepository.Freshness.Cached) + // 延迟后刷新 + delay(300) val fresh = github.load(GitHubRepository.Freshness.Revalidate) _feed.value = fresh _refreshing.value = false _windowChanged.value = false } - // ============================================================ - // 3. 监听窗口变化(用户调整时间范围) viewModelScope.launch { ServiceLocator.settings.activityWindowMonths.drop(1).collect { _windowChanged.value = true @@ -349,7 +346,6 @@ class HomeViewModel( } } - // 4. 监听框架状态变化 viewModelScope.launch { combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } .collect { service -> From f1c87c76a8938f88fee536f482634cb35e7277fb Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 17:29:03 +0800 Subject: [PATCH 12/26] Add files via upload --- .../matrix/vector/manager/ui/screens/home/HomeScreen.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeScreen.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeScreen.kt index b6d00feb4..227c6d2af 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeScreen.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeScreen.kt @@ -686,14 +686,14 @@ private fun QuarterHeadline(feed: CommunityFeed, windowChanged: Boolean) { // changed, so what is on screen was re-cut from disk and may not reach as far as the new // window does. It takes precedence over the other three because it is the newest fact and // the only actionable one. - if (windowChanged || feed.offline || feed.fromCache) { + // 只保留窗口变更和离线,删除"暂歇中" + if (windowChanged || feed.offline) { Spacer(Modifier.height(6.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Icon( when { windowChanged -> Icons.Rounded.Refresh - feed.offline -> Icons.Rounded.CloudOff - else -> Icons.Rounded.Bedtime + else -> Icons.Rounded.CloudOff }, contentDescription = null, modifier = Modifier.height(14.dp), @@ -707,8 +707,7 @@ private fun QuarterHeadline(feed: CommunityFeed, windowChanged: Boolean) { stringResource( when { windowChanged -> R.string.home_window_changed - feed.offline -> R.string.home_offline - else -> R.string.home_resting + else -> R.string.home_offline } ), style = MaterialTheme.typography.labelSmall, From 54fa056f1ef8fef5238aec5051c326c3c30f6ab3 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 18:32:32 +0800 Subject: [PATCH 13/26] Center and enhance ApiBadge and ModuleRow UI ApiBadge now uses a marquee effect, centers content, and clips overflow for better appearance. ModuleRow's icon and badge columns are centered and fixed-width, improving alignment and consistency. These changes enhance UI clarity and prevent layout shifts. --- .../kotlin/org/matrix/vector/ui/ApiBadge.kt | 14 ++++++- .../kotlin/org/matrix/vector/ui/ModuleRow.kt | 41 ++++++++++++------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt index d34ecbe71..6c9a22e05 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.foundation.basicMarquee +import androidx.compose.ui.text.style.TextOverflow /** * `API 101` / `Xposed 93`, with the scale name small and quiet and the number carrying the colour. @@ -24,7 +26,13 @@ import androidx.compose.ui.unit.sp @Composable fun ApiBadge(label: String, value: String, incompatible: Boolean = false) { val colors = MaterialTheme.colorScheme - Row(verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.spacedBy(3.dp)) { + Row( + modifier = Modifier + .fillMaxWidth() + .basicMarquee(iterations = 3, repeatDelayMillis = 2000), + horizontalArrangement = Arrangement.Center, // ← 居中 + verticalAlignment = Alignment.Bottom, + ) { Text( text = label, style = MaterialTheme.typography.labelSmall.copy(fontSize = 8.sp), @@ -35,6 +43,8 @@ fun ApiBadge(label: String, value: String, incompatible: Boolean = false) { style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.SemiBold, color = if (incompatible) colors.error else colors.primary, + maxLines = 1, + overflow = TextOverflow.Clip, ) } -} +} \ No newline at end of file diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt index ff9d1325e..e10db1197 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt @@ -132,24 +132,29 @@ fun ModuleRow( // leaving the list; a bare tap only reports state, since a one-tap toggle would fire whenever // a thumb brushed the list. Column( - modifier = - if (onIconClick != null) - Modifier.contextClickable(onClick = onIconClick, onLongClick = onIconLongClick) - else Modifier, - // Against the text, not centred over the badge: the badge below is wider than the icon, - // so centring left a gap between the icon and the edge the names all start from. - horizontalAlignment = Alignment.End, + modifier = Modifier.width(72.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { - // Fixed at the icon's size whatever is drawn inside, so selecting a module cannot resize - // its row — a tick larger than the icon would grow this box and reflow the list. - Box(modifier = Modifier.size(ICON_SIZE), contentAlignment = Alignment.Center) { + Box( + modifier = Modifier + .size(ICON_SIZE) + .clip(CircleShape) + .then( + if (onIconClick != null) + Modifier.contextClickable( + onClick = onIconClick, + onLongClick = onIconLongClick, + ) + else Modifier + ), + contentAlignment = Alignment.Center, + ) { icon() if (selected) { Box( - modifier = - Modifier.fillMaxSize() - .clip(CircleShape) - .background(colors.primary.copy(alpha = 0.85f)), + modifier = Modifier.fillMaxSize() + .clip(CircleShape) + .background(colors.primary.copy(alpha = 0.85f)), contentAlignment = Alignment.Center, ) { Icon( @@ -162,7 +167,13 @@ fun ModuleRow( } } Spacer(Modifier.height(6.dp)) - apiBadge() + // API Badge 固定宽度居中 + Box( + modifier = Modifier.width(60.dp), + contentAlignment = Alignment.Center, + ) { + apiBadge() + } } Spacer(Modifier.width(16.dp)) From 71b76dc2ca39987257ae5fa193244454663eaf16 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 18:37:22 +0800 Subject: [PATCH 14/26] Update ApiBadge.kt --- manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt index 6c9a22e05..59e7a4c35 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.foundation.basicMarquee import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.Modifier /** * `API 101` / `Xposed 93`, with the scale name small and quiet and the number carrying the colour. From 53765e426cdbf7f9462b1453c4309cd1254fd42b Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 18:42:43 +0800 Subject: [PATCH 15/26] Update ApiBadge.kt --- manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt index 59e7a4c35..bf4591c1b 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.unit.sp import androidx.compose.foundation.basicMarquee import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.Modifier +import androidx.compose.foundation.layout.fillMaxWidth /** * `API 101` / `Xposed 93`, with the scale name small and quiet and the number carrying the colour. From 7763e7730fe0ddf6b5edbfecb1bccbe2e97b3c95 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 19:30:30 +0800 Subject: [PATCH 16/26] Refine ApiBadge and ModuleRow UI layout and styling Updated ApiBadge to use labelMedium typography, increased value font weight, and simplified layout for better clarity. In ModuleRow, removed icon clipping to preserve native app icon shapes and adjusted API badge width for improved alignment. These changes enhance visual consistency and maintainability. --- .../kotlin/org/matrix/vector/ui/ApiBadge.kt | 36 ++++++------------- .../kotlin/org/matrix/vector/ui/ModuleRow.kt | 5 ++- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt index bf4591c1b..a6a33849d 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt @@ -2,51 +2,37 @@ package org.matrix.vector.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.foundation.basicMarquee -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.Modifier -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.ui.text.font.FontWeight -/** - * `API 101` / `Xposed 93`, with the scale name small and quiet and the number carrying the colour. - * - * The scale name is context that rarely changes and repeats down every row; the number is the fact - * being checked, so it is the only part given weight and colour. A caller with no value to show - * passes `"?"` for [value] and `incompatible = true`, which keeps the badge the same shape while the - * missing number reads as missing rather than as a different kind of thing. - * - * Model-agnostic on purpose: Vector maps a module's declared API into ([label], [value]); LSPatch - * passes its own text. Both get one badge instead of two that drift apart. - */ @Composable fun ApiBadge(label: String, value: String, incompatible: Boolean = false) { val colors = MaterialTheme.colorScheme Row( - modifier = Modifier - .fillMaxWidth() - .basicMarquee(iterations = 3, repeatDelayMillis = 2000), - horizontalArrangement = Arrangement.Center, // ← 居中 + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.Bottom, ) { Text( text = label, - style = MaterialTheme.typography.labelSmall.copy(fontSize = 8.sp), + style = MaterialTheme.typography.labelMedium, + color = colors.onSurfaceVariant.copy(alpha = 0.7f), + ) + Text( + text = " ", + style = MaterialTheme.typography.labelMedium, color = colors.onSurfaceVariant.copy(alpha = 0.7f), ) Text( text = value, style = MaterialTheme.typography.labelMedium, - fontWeight = FontWeight.SemiBold, + fontWeight = FontWeight.Black, color = if (incompatible) colors.error else colors.primary, - maxLines = 1, - overflow = TextOverflow.Clip, ) } } \ No newline at end of file diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt index e10db1197..c62fb2420 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt @@ -135,10 +135,10 @@ fun ModuleRow( modifier = Modifier.width(72.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { + // 图标 - 不裁剪,保持应用原生形状 Box( modifier = Modifier .size(ICON_SIZE) - .clip(CircleShape) .then( if (onIconClick != null) Modifier.contextClickable( @@ -167,9 +167,8 @@ fun ModuleRow( } } Spacer(Modifier.height(6.dp)) - // API Badge 固定宽度居中 Box( - modifier = Modifier.width(60.dp), + modifier = Modifier.width(70.dp), contentAlignment = Alignment.Center, ) { apiBadge() From 39e06e7a9a57ba0001acea002516759c97c7ecdb Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 20:04:07 +0800 Subject: [PATCH 17/26] Improve UI badge text handling and icon clipping Added maxLines and overflow handling to ApiBadge text to prevent overflow issues. Updated ModuleRow to clip icons with rounded corners and slightly increased the badge width for better alignment. These changes enhance UI consistency and robustness. --- .../src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt | 8 +++++++- .../src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt index a6a33849d..e48cc871f 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow @Composable fun ApiBadge(label: String, value: String, incompatible: Boolean = false) { @@ -20,19 +21,24 @@ fun ApiBadge(label: String, value: String, incompatible: Boolean = false) { ) { Text( text = label, - style = MaterialTheme.typography.labelMedium, + style = MaterialTheme.typography.labelMedium, // ← 统一用 labelMedium color = colors.onSurfaceVariant.copy(alpha = 0.7f), + maxLines = 1, + overflow = TextOverflow.Clip, ) Text( text = " ", style = MaterialTheme.typography.labelMedium, color = colors.onSurfaceVariant.copy(alpha = 0.7f), + maxLines = 1, ) Text( text = value, style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.Black, color = if (incompatible) colors.error else colors.primary, + maxLines = 1, + overflow = TextOverflow.Clip, ) } } \ No newline at end of file diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt index c62fb2420..d6754c468 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt @@ -139,6 +139,7 @@ fun ModuleRow( Box( modifier = Modifier .size(ICON_SIZE) + .clip(RoundedCornerShape(8.dp)) .then( if (onIconClick != null) Modifier.contextClickable( @@ -168,7 +169,7 @@ fun ModuleRow( } Spacer(Modifier.height(6.dp)) Box( - modifier = Modifier.width(70.dp), + modifier = Modifier.width(72.dp), contentAlignment = Alignment.Center, ) { apiBadge() From ddfa803e2ed25619cb78bdca883c1125f93ed67e Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 20:32:37 +0800 Subject: [PATCH 18/26] Update ApiBadge.kt --- manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt index e48cc871f..2964b41a5 100644 --- a/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt +++ b/manager-ui/src/main/kotlin/org/matrix/vector/ui/ApiBadge.kt @@ -21,14 +21,14 @@ fun ApiBadge(label: String, value: String, incompatible: Boolean = false) { ) { Text( text = label, - style = MaterialTheme.typography.labelMedium, // ← 统一用 labelMedium + style = MaterialTheme.typography.labelSmall, // ← 统一用 labelMedium color = colors.onSurfaceVariant.copy(alpha = 0.7f), maxLines = 1, overflow = TextOverflow.Clip, ) Text( text = " ", - style = MaterialTheme.typography.labelMedium, + style = MaterialTheme.typography.labelSmall, color = colors.onSurfaceVariant.copy(alpha = 0.7f), maxLines = 1, ) From 8f8486669b42dcd0638d62c2b68d09e79d90965e Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 21:26:43 +0800 Subject: [PATCH 19/26] Update HomeViewModel.kt --- .../manager/ui/screens/home/HomeViewModel.kt | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index ac0055384..d11cd96e8 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -328,33 +328,56 @@ class HomeViewModel( init { refreshPresence() - viewModelScope.launch { - // 立即显示缓存 - _feed.value = github.load(GitHubRepository.Freshness.Cached) - // 延迟后刷新 - delay(300) - val fresh = github.load(GitHubRepository.Freshness.Revalidate) - _feed.value = fresh - _refreshing.value = false - _windowChanged.value = false - } + val cached = github.load(GitHubRepository.Freshness.Cached) - viewModelScope.launch { - ServiceLocator.settings.activityWindowMonths.drop(1).collect { - _windowChanged.value = true - _feed.value = github.load(GitHubRepository.Freshness.Cached) + if (cached.commits.isNotEmpty() || cached.loaded) { + // ==================== 有缓存:秒开 ==================== + _feed.value = cached + + // 300ms 后后台刷新 + viewModelScope.launch { + delay(300) + val fresh = github.load(GitHubRepository.Freshness.Revalidate) + _feed.value = fresh + _refreshing.value = false + _windowChanged.value = false } - } - - viewModelScope.launch { - combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } - .collect { service -> - refreshStatus(service) - if (service != null) refreshToggles() + // ==================================================== + } else { + // ==================== 无缓存:立即联网 ==================== + // 显示"加载中..."(和原始代码一样) + _feed.value = CommunityFeed() + + viewModelScope.launch { + val fresh = github.load(GitHubRepository.Freshness.Revalidate) + // 联网失败时显示"离线",而不是"没有动态" + _feed.value = if (fresh.commits.isEmpty() && !fresh.loaded) { + CommunityFeed().copy(loaded = true, offline = true) + } else { + fresh } + _refreshing.value = false + _windowChanged.value = false + } + // ==================================================== + } + + viewModelScope.launch { + ServiceLocator.settings.activityWindowMonths.drop(1).collect { + _windowChanged.value = true + _feed.value = github.load(GitHubRepository.Freshness.Cached) } } + viewModelScope.launch { + combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } + .collect { service -> + refreshStatus(service) + if (service != null) refreshToggles() + } + } +} + private suspend fun refreshStatus(service: IManagerService?) { if (service == null || !daemon.isAlive) { // A binder did arrive and was refused for speaking a different generation of the From 95646c1668fae20654bed1e51417930ac7e54c87 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 21:38:23 +0800 Subject: [PATCH 20/26] Update HomeViewModel.kt --- .../manager/ui/screens/home/HomeViewModel.kt | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index d11cd96e8..09890f5db 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -328,29 +328,19 @@ class HomeViewModel( init { refreshPresence() - val cached = github.load(GitHubRepository.Freshness.Cached) - - if (cached.commits.isNotEmpty() || cached.loaded) { - // ==================== 有缓存:秒开 ==================== - _feed.value = cached + viewModelScope.launch { + val cached = github.load(GitHubRepository.Freshness.Cached) - // 300ms 后后台刷新 - viewModelScope.launch { + if (cached.commits.isNotEmpty() || cached.loaded) { + _feed.value = cached delay(300) val fresh = github.load(GitHubRepository.Freshness.Revalidate) _feed.value = fresh _refreshing.value = false _windowChanged.value = false - } - // ==================================================== - } else { - // ==================== 无缓存:立即联网 ==================== - // 显示"加载中..."(和原始代码一样) - _feed.value = CommunityFeed() - - viewModelScope.launch { + } else { + _feed.value = CommunityFeed() val fresh = github.load(GitHubRepository.Freshness.Revalidate) - // 联网失败时显示"离线",而不是"没有动态" _feed.value = if (fresh.commits.isEmpty() && !fresh.loaded) { CommunityFeed().copy(loaded = true, offline = true) } else { @@ -359,24 +349,23 @@ class HomeViewModel( _refreshing.value = false _windowChanged.value = false } - // ==================================================== } - - viewModelScope.launch { - ServiceLocator.settings.activityWindowMonths.drop(1).collect { - _windowChanged.value = true - _feed.value = github.load(GitHubRepository.Freshness.Cached) - } - } - - viewModelScope.launch { - combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } - .collect { service -> - refreshStatus(service) - if (service != null) refreshToggles() + + viewModelScope.launch { + ServiceLocator.settings.activityWindowMonths.drop(1).collect { + _windowChanged.value = true + _feed.value = github.load(GitHubRepository.Freshness.Cached) } + } + + viewModelScope.launch { + combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } + .collect { service -> + refreshStatus(service) + if (service != null) refreshToggles() + } + } } -} private suspend fun refreshStatus(service: IManagerService?) { if (service == null || !daemon.isAlive) { From bc20be13f3e53d598949c2a5d8c6c0298c2191d3 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 21:50:46 +0800 Subject: [PATCH 21/26] Update HomeViewModel.kt --- .../manager/ui/screens/home/HomeViewModel.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index 09890f5db..e8f60cb41 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -332,22 +332,27 @@ class HomeViewModel( val cached = github.load(GitHubRepository.Freshness.Cached) if (cached.commits.isNotEmpty() || cached.loaded) { + // ========== 有缓存:秒开 ========== _feed.value = cached delay(300) val fresh = github.load(GitHubRepository.Freshness.Revalidate) _feed.value = fresh _refreshing.value = false _windowChanged.value = false + // ================================== } else { + // ========== 无缓存:显示"加载中" ========== _feed.value = CommunityFeed() - val fresh = github.load(GitHubRepository.Freshness.Revalidate) - _feed.value = if (fresh.commits.isEmpty() && !fresh.loaded) { - CommunityFeed().copy(loaded = true, offline = true) - } else { - fresh + + viewModelScope.launch { + val fresh = github.load(GitHubRepository.Freshness.Revalidate) + // 联网成功 → 显示数据 + // 联网失败 → 保持"加载中"(和原始代码一致) + _feed.value = fresh + _refreshing.value = false + _windowChanged.value = false } - _refreshing.value = false - _windowChanged.value = false + // ======================================== } } From 07ad2502932e89d94807fe1abdb05c5964aca54c Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 22:04:56 +0800 Subject: [PATCH 22/26] Update HomeViewModel.kt --- .../vector/manager/ui/screens/home/HomeViewModel.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index e8f60cb41..73bf0970a 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -339,20 +339,21 @@ class HomeViewModel( _feed.value = fresh _refreshing.value = false _windowChanged.value = false - // ================================== } else { // ========== 无缓存:显示"加载中" ========== - _feed.value = CommunityFeed() + _feed.value = CommunityFeed() // loaded = false → "加载中" viewModelScope.launch { val fresh = github.load(GitHubRepository.Freshness.Revalidate) - // 联网成功 → 显示数据 - // 联网失败 → 保持"加载中"(和原始代码一致) - _feed.value = fresh + // 保持"加载中"直到真正有数据 + _feed.value = if (fresh.commits.isNotEmpty()) { + fresh + } else { + CommunityFeed() // 保持 loaded = false → "加载中" + } _refreshing.value = false _windowChanged.value = false } - // ======================================== } } From 1ef817e329ac1663f8602c6440b44bcc3beced43 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 22:33:42 +0800 Subject: [PATCH 23/26] Update HomeViewModel.kt --- .../matrix/vector/manager/ui/screens/home/HomeViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index 73bf0970a..5f98409ec 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -341,15 +341,15 @@ class HomeViewModel( _windowChanged.value = false } else { // ========== 无缓存:显示"加载中" ========== - _feed.value = CommunityFeed() // loaded = false → "加载中" + _feed.value = CommunityFeed().copy(loaded = false) // ← 强制 loaded = false viewModelScope.launch { val fresh = github.load(GitHubRepository.Freshness.Revalidate) - // 保持"加载中"直到真正有数据 + // 只有真正有数据时才更新,否则保持"加载中" _feed.value = if (fresh.commits.isNotEmpty()) { fresh } else { - CommunityFeed() // 保持 loaded = false → "加载中" + CommunityFeed().copy(loaded = false) // ← 强制 loaded = false } _refreshing.value = false _windowChanged.value = false From 61169d88276b6224ea34970ace19e1ec7daf6eb3 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 23:30:50 +0800 Subject: [PATCH 24/26] Update HomeViewModel.kt --- .../manager/ui/screens/home/HomeViewModel.kt | 76 +++++++++++++------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index 5f98409ec..e5a5719d2 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.flowOn @@ -327,52 +326,79 @@ class HomeViewModel( init { refreshPresence() - + // The binder may arrive after this ViewModel exists — injection order is not ours to + // control — so status is re-derived whenever it changes rather than read once in init. + viewModelScope.launch { + // Both flows, because a refused binder leaves `service` null and only moves + // `peerDescriptor`. Collecting `service` alone would see no change at all — it was + // already null — and the header would sit on "not activated" for a framework that is + // running and simply out of step with this build. + combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } + .collect { service -> + refreshStatus(service) + // Both switches on the status page hold the daemon's state rather than ours, + // and the binder is what they need. This runs for every binder, including one + // already in hand when `refreshPresence` ran above — a second read of two + // idempotent values — and it is here for the one that arrives afterwards, + // which that call found nothing to ask about and returned. Nor is it the last + // such moment: `refreshPresence` asks again whenever a screen that shows them + // is opened, since this flow does not emit a second time while one binder + // stays alive. + if (service != null) refreshToggles() + } + } + + // ============================================= + // Feed 加载逻辑:有缓存秒开,无缓存显示加载中 + // ============================================= viewModelScope.launch { val cached = github.load(GitHubRepository.Freshness.Cached) - - if (cached.commits.isNotEmpty() || cached.loaded) { - // ========== 有缓存:秒开 ========== + if (cached.commits.isNotEmpty()) { + // 有缓存:秒开 _feed.value = cached delay(300) - val fresh = github.load(GitHubRepository.Freshness.Revalidate) - _feed.value = fresh + try { + val fresh = github.load(GitHubRepository.Freshness.Revalidate) + if (fresh.commits.isNotEmpty()) { + _feed.value = fresh + } + } catch (_: Exception) { + // 网络失败,保留缓存 + } _refreshing.value = false _windowChanged.value = false } else { - // ========== 无缓存:显示"加载中" ========== - _feed.value = CommunityFeed().copy(loaded = false) // ← 强制 loaded = false - - viewModelScope.launch { + // 无缓存:显示加载中,立即联网 + _feed.value = CommunityFeed().copy(loaded = false) + _refreshing.value = true + try { val fresh = github.load(GitHubRepository.Freshness.Revalidate) - // 只有真正有数据时才更新,否则保持"加载中" - _feed.value = if (fresh.commits.isNotEmpty()) { - fresh + if (fresh.commits.isNotEmpty()) { + _feed.value = fresh } else { - CommunityFeed().copy(loaded = false) // ← 强制 loaded = false + _feed.value = CommunityFeed().copy(loaded = true) } + } catch (_: Exception) { + // 网络失败,保持 loaded = false,用户可下拉刷新 + } finally { _refreshing.value = false _windowChanged.value = false } } } - + viewModelScope.launch { + // drop(1): the value on subscription is the window already rendered. ServiceLocator.settings.activityWindowMonths.drop(1).collect { _windowChanged.value = true + // From disk, not from GitHub. The archive already holds the commits; the window is + // only a view of it, so re-cutting it costs nothing and the feed answers on the + // frame after the sheet closes. _feed.value = github.load(GitHubRepository.Freshness.Cached) } } - - viewModelScope.launch { - combine(ServiceLocator.service, ServiceLocator.peerMismatch) { service, _ -> service } - .collect { service -> - refreshStatus(service) - if (service != null) refreshToggles() - } - } } - + private suspend fun refreshStatus(service: IManagerService?) { if (service == null || !daemon.isAlive) { // A binder did arrive and was refused for speaking a different generation of the From 812f0eb598fa27e253d671a1203a7df3e44678d1 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Thu, 27 Aug 2026 23:37:51 +0800 Subject: [PATCH 25/26] Update HomeViewModel.kt --- .../org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt index e5a5719d2..9d7845d0b 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/home/HomeViewModel.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlin.random.Random import kotlinx.coroutines.launch +import kotlinx.coroutines.delay import org.matrix.vector.ipc.IManagerService import org.matrix.vector.manager.data.github.CommunityFeed import org.matrix.vector.manager.data.github.GitHubRepository From 9aea08b3650b27dcaa55d1a786b2ce6c0e25b930 Mon Sep 17 00:00:00 2001 From: lsfqxd2006 Date: Fri, 28 Aug 2026 21:55:11 +0800 Subject: [PATCH 26/26] Add files via upload --- .../manager/data/repository/FrameworkUpdateRepository.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/data/repository/FrameworkUpdateRepository.kt b/manager/src/main/kotlin/org/matrix/vector/manager/data/repository/FrameworkUpdateRepository.kt index 7a3c0c006..b82cdf75f 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/data/repository/FrameworkUpdateRepository.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/data/repository/FrameworkUpdateRepository.kt @@ -126,8 +126,5 @@ enum class ReleaseDirection { * row anywhere marked as installed. */ fun FrameworkUpdateState.divergesFrom(release: FrameworkRelease?): Boolean { - if (release == null || release.versionCode != installedVersionCode) return false - val mine = buildStamp(installedCommit ?: return false) - if (mine.commit == null || release.commit == null) return false - return !mine.isCommit(release.commit) + return false }