1 Commits

Author SHA1 Message Date
Rene Kievits
3da110d6cc add auto update for android
All checks were successful
Release Build / build-docker (push) Successful in 41s
Release Build / build-android-and-release (push) Successful in 2m35s
2025-12-26 22:57:40 +01:00
12 changed files with 103 additions and 7 deletions

View File

@@ -73,6 +73,7 @@ jobs:
run: | run: |
npm ci npm ci
npm run build:android npm run build:android
zip -r dist.zip dist
- name: Sync Capacitor to Android - name: Sync Capacitor to Android
working-directory: client working-directory: client
@@ -105,4 +106,5 @@ jobs:
- Client: `${{ vars.REGISTRY }}/${{ vars.CLIENT_IMAGE }}:latest` - Client: `${{ vars.REGISTRY }}/${{ vars.CLIENT_IMAGE }}:latest`
files: | files: |
client/android/app/build/outputs/apk/release/*.apk client/android/app/build/outputs/apk/release/*.apk
client/dist.zip
api_key: ${{ secrets.GITHUB_TOKEN }} api_key: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -9,7 +9,8 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-app')
implementation project(':capgo-capacitor-updater')
} }

View File

@@ -1,3 +1,9 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
include ':capgo-capacitor-updater'
project(':capgo-capacitor-updater').projectDir = new File('../node_modules/@capgo/capacitor-updater/android')

View File

@@ -5,5 +5,10 @@
"server": { "server": {
"androidScheme": "https", "androidScheme": "https",
"cleartext": false "cleartext": false
},
"plugins": {
"CapacitorUpdater": {
"autoUpdate": false
}
} }
} }

View File

@@ -9,7 +9,9 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@capacitor/android": "^8.0.0", "@capacitor/android": "^8.0.0",
"@capacitor/app": "^8.0.0",
"@capacitor/core": "^8.0.0", "@capacitor/core": "^8.0.0",
"@capgo/capacitor-updater": "^8.40.6",
"@eslint/eslintrc": "^3.3.3", "@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.2",
"@mdi/font": "^7.3.67", "@mdi/font": "^7.3.67",
@@ -639,6 +641,15 @@
"@capacitor/core": "^8.0.0" "@capacitor/core": "^8.0.0"
} }
}, },
"node_modules/@capacitor/app": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/app/-/app-8.0.0.tgz",
"integrity": "sha512-OwzIkUs4w433Bu9WWAEbEYngXEfJXZ9Wmdb8eoaqzYBgB0W9/3Ed/mh6sAYPNBAZlpyarmewgP7Nb+d3Vrh+xA==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=8.0.0"
}
},
"node_modules/@capacitor/cli": { "node_modules/@capacitor/cli": {
"version": "7.4.4", "version": "7.4.4",
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.4.tgz", "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.4.tgz",
@@ -681,6 +692,15 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@capgo/capacitor-updater": {
"version": "8.40.6",
"resolved": "https://registry.npmjs.org/@capgo/capacitor-updater/-/capacitor-updater-8.40.6.tgz",
"integrity": "sha512-Z2uG8E9VKClYeZRhvkN8p/w3tXqxj5DB61M3uKwuHyHgPWEOoBv+drlRtnwDL8+XhSljNLG7yGPWUg8WAi8cBQ==",
"license": "MPL-2.0",
"peerDependencies": {
"@capacitor/core": "^8.0.0"
}
},
"node_modules/@csstools/css-parser-algorithms": { "node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",

View File

@@ -14,7 +14,9 @@
}, },
"dependencies": { "dependencies": {
"@capacitor/android": "^8.0.0", "@capacitor/android": "^8.0.0",
"@capacitor/app": "^8.0.0",
"@capacitor/core": "^8.0.0", "@capacitor/core": "^8.0.0",
"@capgo/capacitor-updater": "^8.40.6",
"@eslint/eslintrc": "^3.3.3", "@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.2",
"@mdi/font": "^7.3.67", "@mdi/font": "^7.3.67",

View File

@@ -287,7 +287,9 @@ import {
ref, watch, onMounted, computed, onUnmounted, ref, watch, onMounted, computed, onUnmounted,
} from 'vue'; } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { App as CapacitorApp } from '@capacitor/app';
import { useAppStore } from '@/stores/appStore'; import { useAppStore } from '@/stores/appStore';
import { checkForUpdates } from '@/utils/autoUpdate';
import logo from '@/assets/icon.svg'; import logo from '@/assets/icon.svg';
const drawer = ref(false); const drawer = ref(false);
@@ -388,6 +390,9 @@ const handleMouseMove = (e) => {
}; };
onMounted(() => { onMounted(() => {
CapacitorApp.addListener('appStateChange', ({ isActive }) => {
if (isActive) checkForUpdates();
});
if (store.token) { if (store.token) {
viewState.value = 'app'; viewState.value = 'app';
store.fetchStats(); store.fetchStats();

View File

@@ -25,10 +25,10 @@ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes: [ routes: [
{ path: '/', component: Dashboard }, { path: '/', component: Dashboard },
{ path: '/dashboard', component: Dashboard },
{ path: '/collection', component: Collection }, { path: '/collection', component: Collection },
{ path: '/review', component: Review }, { path: '/review', component: Review },
{ path: '/lesson', component: Lesson }, { path: '/lesson', component: Lesson },
{ path: '/:pathMatch(.*)*', redirect: '/' },
], ],
}); });

View File

@@ -0,0 +1,55 @@
import { Capacitor } from '@capacitor/core';
import { App } from '@capacitor/app';
export async function checkForUpdates() {
if (!Capacitor.isNativePlatform()) {
console.log('Auto-update skipped (running on web)');
return;
}
function isNewer(current, target) {
const c = current.replace(/^v/, '').split('.').map(Number);
const t = target.replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < 3; i++) {
if (t[i] > c[i]) return true;
if (t[i] < c[i]) return false;
}
return false;
}
try {
const { CapacitorUpdater } = await import('@capgo/capacitor-updater');
await CapacitorUpdater.notifyAppReady();
const GITEA_API_URL = 'https://git.crylia.de/Crylia/zen-kanji/releases/latest';
const appInfo = await App.getInfo();
const currentVersion = appInfo.version;
const response = await fetch(GITEA_API_URL);
if (!response.ok) return;
const release = await response.json();
const latestTag = release.tag_name;
if (!isNewer(currentVersion, latestTag)) {
return;
}
const asset = release.assets.find((a) => a.name === 'dist.zip');
if (!asset) return;
console.log(`Downloading update: ${latestTag}`);
const version = await CapacitorUpdater.download({
url: asset.browser_download_url,
version: latestTag,
});
await CapacitorUpdater.set(version);
} catch (error) {
console.error('Auto-update failed:', error);
}
}

View File

@@ -8,7 +8,7 @@
v-else-if="currentItem" v-else-if="currentItem"
) )
.d-flex.align-center.pa-4.border-b-subtle .d-flex.align-center.pa-4.border-b-subtle
v-btn(icon="mdi-close" variant="text" to="/dashboard" color="grey") v-btn(icon="mdi-close" variant="text" to="/" color="grey")
v-spacer v-spacer
.text-caption.text-grey-darken-1.font-weight-bold .text-caption.text-grey-darken-1.font-weight-bold
| {{ sessionDone + 1 }} / {{ sessionTotal }} | {{ sessionDone + 1 }} / {{ sessionTotal }}
@@ -95,7 +95,7 @@
.text-h4.font-weight-bold.text-white.mb-2 {{ $t('lesson.completeTitle') }} .text-h4.font-weight-bold.text-white.mb-2 {{ $t('lesson.completeTitle') }}
.text-body-1.text-grey.mb-8 {{ $t('lesson.learned', { n: sessionDone }) }} .text-body-1.text-grey.mb-8 {{ $t('lesson.learned', { n: sessionDone }) }}
v-btn.glow-btn.text-black.font-weight-bold( v-btn.glow-btn.text-black.font-weight-bold(
to="/dashboard" to="/"
block block
color="cyan" color="cyan"
height="50" height="50"

View File

@@ -96,7 +96,7 @@
.text-caption.text-grey.text-uppercase.mt-1 {{ $t('stats.correct') }} .text-caption.text-grey.text-uppercase.mt-1 {{ $t('stats.correct') }}
v-btn.text-black.font-weight-bold.glow-btn( v-btn.text-black.font-weight-bold.glow-btn(
to="/dashboard" to="/"
block block
color="primary" color="primary"
height="50" height="50"
@@ -108,7 +108,7 @@
.text-h4.mb-2.text-white {{ $t('review.caughtUp') }} .text-h4.mb-2.text-white {{ $t('review.caughtUp') }}
.text-grey.mb-6 {{ $t('review.noReviews') }} .text-grey.mb-6 {{ $t('review.noReviews') }}
v-btn.font-weight-bold( v-btn.font-weight-bold(
to="/dashboard" to="/"
color="primary" color="primary"
variant="tonal" variant="tonal"
) {{ $t('review.viewCollection') }} ) {{ $t('review.viewCollection') }}

View File

@@ -93,7 +93,7 @@ export const getUserStats = async (user) => {
accuracy: Math.round((item.stats.correct / item.stats.total) * 100) accuracy: Math.round((item.stats.correct / item.stats.total) * 100)
})).filter(i => i.accuracy < 85) })).filter(i => i.accuracy < 85)
.sort((a, b) => a.accuracy - b.accuracy) .sort((a, b) => a.accuracy - b.accuracy)
.slice(0, 4); .slice(0, 10);
return { return {
distribution: dist, distribution: dist,