feat(android): auto updater

This commit is contained in:
NoCrypt 2024-08-05 23:15:06 +07:00
parent 2ff58ca5bb
commit ce5f6c88c3
15 changed files with 6269 additions and 1445 deletions

View file

@ -37,10 +37,35 @@ android {
abi {
enable gradle.startParameter.taskNames.any { it.contains("Release") } // https://stackoverflow.com/a/39950584
reset()
include "armeabi-v7a", "arm64-v8a", "x86_64"
include "arm64-v8a", "armeabi-v7a", "x86_64"
universalApk true
}
}
// Map for the version code
project.ext.versionCodes = ['arm64-v8a': 1, 'armeabi-v7a': 2, 'x86_64': 3, 'universal': 4, 'debug': 5]
android.applicationVariants.all { variant ->
// Assign different version code for each output
variant.outputs.each { output ->
def versionCodes = project.ext.versionCodes
def baseVersionCode = android.defaultConfig.versionCode
def abiVersionCode = 0
if (output.getFilter(com.android.build.OutputFile.ABI)) {
abiVersionCode = versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0)
} else {
// This is for the universal APK
abiVersionCode = versionCodes.get('universal', 0)
}
if (gradle.startParameter.taskNames.any { it.contains("Release") }) {
output.versionCodeOverride = abiVersionCode * 1000000 + baseVersionCode
} else {
output.versionCodeOverride = versionCodes.get('debug') * 1000000 + baseVersionCode
}
}
}
}
repositories {

View file

@ -9,8 +9,10 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-community-file-opener')
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-local-notifications')
implementation project(':capacitor-status-bar')
implementation project(':capacitor-nodejs')

View file

@ -53,6 +53,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
</manifest>

View file

@ -27,4 +27,5 @@ public class MainActivity extends BridgeActivity {
}
});
}
}
}
}

View file

@ -2,12 +2,18 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../node_modules/@capacitor/android/capacitor')
include ':capacitor-community-file-opener'
project(':capacitor-community-file-opener').projectDir = new File('../../node_modules/@capacitor-community/file-opener/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../node_modules/@capacitor/app/android')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../../node_modules/@capacitor/browser/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../../node_modules/@capacitor/filesystem/android')
include ':capacitor-local-notifications'
project(':capacitor-local-notifications').projectDir = new File('../../node_modules/@capacitor/local-notifications/android')

View file

@ -27,10 +27,12 @@
"webpack-merge": "^5.10.0"
},
"dependencies": {
"@capacitor-community/file-opener": "^6.0.0",
"@capacitor/android": "^6.1.1",
"@capacitor/app": "^6.0.0",
"@capacitor/browser": "^6.0.1",
"@capacitor/core": "^6.1.1",
"@capacitor/filesystem": "^6.0.0",
"@capacitor/ios": "^6.1.1",
"@capacitor/local-notifications": "^6.0.0",
"@capacitor/status-bar": "^6.0.0",

View file

@ -1,6 +1,7 @@
import { App } from '@capacitor/app'
import { NodeJS } from 'capacitor-nodejs'
import EventEmitter from 'events'
import { AutoUpdater } from './update'
const ready = NodeJS.whenReady()
@ -33,3 +34,15 @@ main.once('version', async () => {
const { version } = await App.getInfo()
main.emit('version', version)
})
const updater = new AutoUpdater('https://api.github.com/repos/NoCrypt/migu/releases/latest');
main.on('update', async () => {
console.log('[Android Updater] Checking for update...')
await updater.initialize()
// await updater.performUpdate();
if (await updater.checkForUpdate()) main.emit('android-update-available')
})
main.on('android-install-update', async () => {
await updater.performUpdate();
})

View file

@ -2,7 +2,7 @@
export const SUPPORTS = {
offscreenRender: false,
update: false,
update: true,
angle: false,
doh: false,
dht: true,

120
capacitor/src/update.js Normal file
View file

@ -0,0 +1,120 @@
import { App } from '@capacitor/app';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { FileOpener } from '@capacitor-community/file-opener';
export class AutoUpdater {
constructor(githubApiUrl) {
this.githubApiUrl = githubApiUrl;
this.currentVersion = null;
this.appInfo = null;
}
async initialize() {
const info = await App.getInfo();
this.appInfo = info;
this.currentVersion = info.version;
this.cpuArchitecture = await this.getCPUArchitecture();
}
async removeCachedAPKs(){
const list = await Filesystem.readdir({path: './', directory: Directory.Cache})
for (const file of list.files) {
if (file.name.endsWith('.apk')) {
await Filesystem.deleteFile({path: file.name, directory: Directory.Cache})
}
}
}
async getCPUArchitecture() {
const versionMap = {'arm64-v8a': 1, 'armeabi-v7a': 2, 'x86_64': 3, 'universal': 4}; //5: debug
const {build} = this.appInfo;
if (build.length === 7) {
const architectureCode = parseInt(build.substring(0, 1));
console.log(architectureCode)
if (architectureCode < 5) {
for (const [arch, code] of Object.entries(versionMap)) {
if (code === architectureCode) {
return arch;
}
}
}
}
// If version code doesn't match expected format or no match found
return 'universal';
}
async checkForUpdate() {
try {
const response = await fetch(this.githubApiUrl);
const releaseInfo = await response.json();
const latestVersion = releaseInfo.tag_name.replace('v', '');
return this.isNewerVersion(latestVersion, this.currentVersion);
// return true
} catch (error) {
console.error('Error checking for update:', error);
return false;
}
}
isNewerVersion(latestVersion, currentVersion) {
const latest = latestVersion.split('.').map(Number);
const current = currentVersion.split('.').map(Number);
for (let i = 0; i < latest.length; i++) {
if (latest[i] > current[i]) return true;
if (latest[i] < current[i]) return false;
}
return false;
}
async downloadUpdate() {
await this.removeCachedAPKs();
try {
const response = await fetch(this.githubApiUrl);
const releaseInfo = await response.json();
const assetName = `android-Migu-${releaseInfo.tag_name}-${this.cpuArchitecture}.apk`;
const asset = releaseInfo.assets.find(a => a.name === assetName);
if (!asset) {
console.error('Update file not found', assetName);
return null;
}
const fileName = `update-${releaseInfo.tag_name}.apk`;
const result = await Filesystem.downloadFile({
url: asset.browser_download_url,
path: fileName,
directory: Directory.Cache,
});
return result.path;
} catch (error) {
console.error('Error downloading update:', error);
return null;
}
}
async installUpdate(filePath) {
try {
await FileOpener.open({
filePath,
contentType: 'application/vnd.android.package-archive'
});
} catch (error) {
console.error('Error installing update:', error);
}
}
async performUpdate() {
const filePath = await this.downloadUpdate();
if (filePath) {
await this.installUpdate(filePath);
}
}
}

View file

@ -25,6 +25,7 @@
import { Toaster } from 'svelte-sonner'
import Logout from './components/Logout.svelte'
import Navbar from './components/Navbar.svelte'
import { SUPPORTS } from '@/modules/support.js';
setContext('view', view)
</script>
@ -37,7 +38,7 @@
<div class='overflow-hidden content-wrapper h-full z-10'>
<Toaster visibleToasts={6} position='top-right' theme='dark' richColors duration={10000} closeButton toastOptions={{
classes: {
closeButton: "toast-close-button"
closeButton: SUPPORTS.isAndroid ? "toast-close-button" : ""
}
}} style="margin-top: var(--safe-area-top)"/>
@ -50,12 +51,20 @@
<style>
:global(.toast-close-button){
bottom: 10px !important;
right: 10px !important;
bottom: -10px !important;
right: -10px !important;
left: unset !important;
top: unset !important;
}
:global(:where([data-sonner-toast]) :where([data-disabled='true']) ){
opacity: 0 !important;
}
:global(:root){
--normal-bg: var(--dark-color-light) !important;
}
.content-wrapper {
will-change: width;
top: 0 !important;

View file

@ -10,7 +10,7 @@
<script>
import Home from './views/Home/Home.svelte'
import MediaHandler, { media } from './views/Player/MediaHandler.svelte'
import Settings from '@/views/Settings/Settings.svelte'
import Settings, { version } from '@/views/Settings/Settings.svelte'
import WatchTogether from './views/WatchTogether/WatchTogether.svelte'
import Miniplayer from 'svelte-miniplayer'
import Search from './views/Search.svelte'
@ -33,9 +33,43 @@
$: maxwidth = $isMobile ? '200px' : '60rem'
onMount(() => {
// Check update (ask if on Android, install if on PC)
if($settings.enableAutoUpdate && SUPPORTS.update) IPC.emit('update')
if (SUPPORTS.isAndroid) {
// Auto updater for android
IPC.on('android-update-available', () => {
toast.info('Update found', {
description: 'Wanna install it?',
action: {
label: 'Install now',
onClick: () => {
console.log(version)
IPC.emit('android-install-update')
toast.loading('Downloading...', {
description: 'Please allow the permission when asked. ',
duration: Number.POSITIVE_INFINITY,
dismissable: false,
cancel:{
label: 'Hide',
onClick: () => {
toast.dismiss()
}
}
})
}
},
cancel: {
label: 'Never show again',
onClick: () => {
$settings.enableAutoUpdate = false
toast('Auto update disabled. You can re-enable it in Settings > App')
}
},
duration: Number.POSITIVE_INFINITY
})
})
// Back button support
let backButtonPressTimeout;
window.Capacitor.Plugins.App.addListener("backButton", () => {
if (page === "home" && $view === null && $rss === null) {

View file

@ -62,8 +62,8 @@ a[href]:active, button:not([disabled]):active, fieldset:not([disabled]):active,
}
[data-sonner-toaster][data-theme='dark'] {
--normal-bg: var(--dark-color) !important;
--normal-border: none !important;
--normal-bg: var(--dark-color-light) !important;
--normal-border: rgba(128, 128, 128, 0.297) !important;
--normal-text: var(--dm-base-text-color) !important;
/* --success-bg: var(--success-color) !important; */

View file

@ -136,7 +136,7 @@ export const defaults = {
slowSeeding: true,
disableStartupVideo: true,
amoledTheme: true,
enableAutoUpdate: true,
enableAutoUpdate: !SUPPORTS.isAndroid,
torrentPersist: false,
torrentDHT: false,
torrentPeX: false,

View file

@ -16,7 +16,7 @@
sunos: 'SunOS',
win32: 'Windows'
}
let version = '1.0.0'
export let version = '1.0.0'
IPC.on('version', data => (version = data))
IPC.emit('version')

File diff suppressed because it is too large Load diff