Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
Expand Down Expand Up @@ -57,9 +58,14 @@
<data android:mimeType="text/plain" />
</intent-filter>
</activity>

<service
android:name=".ui.page.post.DownloadService"
android:foregroundServiceType="dataSync" />
<service
android:name=".worker.DownloadApkService"
android:foregroundServiceType="dataSync" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/paulcoding/hviewer/Constants.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package com.paulcoding.hviewer

const val CHECK_FOR_UPDATE_CHANNEL = "check_for_update"
const val CHECK_FOR_UPDATE_APK_CHANNEL = "check_for_update_app"
const val ACTION_INSTALL_APK = "ACTION_INSTALL_APK"
8 changes: 8 additions & 0 deletions app/src/main/java/com/paulcoding/hviewer/MainApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.app.NotificationManager
import android.content.Context
import com.paulcoding.hviewer.helper.CrashHandler
import com.paulcoding.hviewer.helper.setupPaths
import com.paulcoding.hviewer.worker.scheduleApkUpdate
import com.paulcoding.hviewer.worker.scheduleScriptsUpdate
import com.paulcoding.js.JS
import com.tencent.mmkv.MMKV
Expand All @@ -25,6 +26,7 @@ class MainApp : Application() {

private fun setupWorkers() {
scheduleScriptsUpdate(this)
scheduleApkUpdate(this)
}

private fun setupNotificationChannels() {
Expand All @@ -34,7 +36,13 @@ class MainApp : Application() {
"Check for update",
NotificationManager.IMPORTANCE_LOW
)
val checkForUpdateApkChannel = NotificationChannel(
CHECK_FOR_UPDATE_APK_CHANNEL,
"Check for update",
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(checkForUpdateChannel)
notificationManager.createNotificationChannel(checkForUpdateApkChannel)
}

companion object {
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/paulcoding/hviewer/model/Github.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.paulcoding.hviewer.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

data class Release(
val url: String,
val id: Int,
val tag_name: String,
val assets: List<Asset>,

)

data class Asset(
val browser_download_url: String,
)

@Parcelize
data class HRelease(
val version: String,
val downloadUrl: String
) : Parcelable
40 changes: 39 additions & 1 deletion app/src/main/java/com/paulcoding/hviewer/network/Github.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.paulcoding.hviewer.network

import android.content.Context
import com.google.gson.Gson
import com.paulcoding.hviewer.BuildConfig
import com.paulcoding.hviewer.MainApp.Companion.appContext
import com.paulcoding.hviewer.R
import com.paulcoding.hviewer.helper.extractTarGzFromResponseBody
import com.paulcoding.hviewer.helper.log
import com.paulcoding.hviewer.helper.readConfigFile
import com.paulcoding.hviewer.model.HRelease
import com.paulcoding.hviewer.model.Release
import com.paulcoding.hviewer.model.SiteConfigs
import com.paulcoding.hviewer.preference.Preferences
import io.ktor.client.call.body
Expand All @@ -14,6 +18,7 @@ import io.ktor.client.statement.readRawBytes
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File

object Github {
@Throws(Exception::class)
Expand Down Expand Up @@ -111,6 +116,39 @@ object Github {
return null
}
}

suspend fun checkForUpdate(
currentVersion: String,
onUpdateAvailable: ((String, String) -> Unit)? = null
): HRelease? {
val (owner, repo) = parseRepo(BuildConfig.REPO_URL)
val url = "https://api.github.com/repos/${owner}/${repo}/releases/latest"
ktorClient.use { client ->
val jsonObject: Release = client.get(url).body()
val latestVersion = jsonObject.tag_name.substring(1)
val downloadUrl = jsonObject.assets[0].browser_download_url
if (latestVersion != currentVersion) {
onUpdateAvailable?.invoke(latestVersion, downloadUrl)
return HRelease(latestVersion, downloadUrl)
}
return null
}
}

suspend fun downloadApk(
context: Context,
downloadUrl: String,
onDownloadComplete: (File) -> Unit
) {
val file = File(context.cacheDir, "latest.apk")
ktorClient.use { client ->
val input = client.get(downloadUrl).readRawBytes().inputStream()
file.outputStream().use { output ->
input.copyTo(output)
}
}
onDownloadComplete(file)
}
}


Expand All @@ -121,7 +159,7 @@ sealed class SiteConfigsState {

fun getToastMessage() = when (this) {
is NewConfigsInstall -> R.string.scripts_installed
is UpToDate -> R.string.up_to_Date
is UpToDate -> R.string.up_to_date
is Updated -> R.string.scripts_updated
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ fun ConfirmDialog(
text: String = "",
confirmColor: Color? = null,
dismissColor: Color? = null,
confirmText: String? = null,
dismissText: String? = null,
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
Expand All @@ -28,15 +30,15 @@ fun ConfirmDialog(
confirmButton = {
TextButton(onClick = { onConfirm() }) {
Text(
stringResource(R.string.confirm),
confirmText ?: stringResource(R.string.confirm),
color = confirmColor ?: MaterialTheme.colorScheme.error
)
}
},
dismissButton = {
TextButton(onClick = { onDismiss() }) {
Text(
stringResource(R.string.cancel),
dismissText ?: stringResource(R.string.cancel),
color = dismissColor ?: MaterialTheme.colorScheme.onBackground
)
}
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/paulcoding/hviewer/ui/page/AppEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
Expand All @@ -24,6 +25,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import com.paulcoding.hviewer.ACTION_INSTALL_APK
import com.paulcoding.hviewer.BuildConfig
import com.paulcoding.hviewer.R
import com.paulcoding.hviewer.helper.makeToast
Expand Down Expand Up @@ -100,6 +102,10 @@ fun AppEntry(intent: Intent?, appViewModel: AppViewModel) {
}
}

ACTION_INSTALL_APK -> {
appViewModel.installApk(context, data.toString().toUri())
}

else -> {
}
}
Expand Down
44 changes: 44 additions & 0 deletions app/src/main/java/com/paulcoding/hviewer/ui/page/AppViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.paulcoding.hviewer.ui.page

import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.paulcoding.hviewer.BuildConfig
Expand Down Expand Up @@ -66,6 +70,7 @@ class AppViewModel : ViewModel() {
val siteConfigs: SiteConfigs? = appContext.readConfigFile<SiteConfigs>().getOrNull(),
val error: Throwable? = null,
val checkingForUpdateScripts: Boolean = false,
val updatingApk: Boolean = false,
)

private fun setError(throwable: Throwable) {
Expand Down Expand Up @@ -185,4 +190,43 @@ class AppViewModel : ViewModel() {
}
}
}

fun checkForUpdate(
currentVersion: String,
onUpToDate: () -> Unit,
onUpdateAvailable: (String, String) -> Unit
) {
viewModelScope.launch {
_stateFlow.update { it.copy(updatingApk = true) }
val release = Github.checkForUpdate(currentVersion)
if (release != null)
onUpdateAvailable(release.version, release.downloadUrl)
else
onUpToDate()
_stateFlow.update { it.copy(updatingApk = false) }
}
}

fun downloadAndInstallApk(context: Context, downloadUrl: String) {
viewModelScope.launch {
_stateFlow.update { it.copy(updatingApk = true) }
Github.downloadApk(context, downloadUrl) { file ->
val uri = FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
file
)
installApk(context, uri)
}
_stateFlow.update { it.copy(updatingApk = false) }
}
}

fun installApk(context: Context, uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/vnd.android.package-archive")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
context.startActivity(intent)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.paulcoding.hviewer.ui.page.settings

import androidx.activity.ComponentActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
Expand All @@ -16,10 +18,12 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BugReport
import androidx.compose.material.icons.outlined.Description
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Update
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchColors
Expand All @@ -35,17 +39,22 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.paulcoding.hviewer.BuildConfig
import com.paulcoding.hviewer.R
import com.paulcoding.hviewer.extensions.setSecureScreen
import com.paulcoding.hviewer.helper.makeToast
import com.paulcoding.hviewer.preference.Preferences
import com.paulcoding.hviewer.ui.component.ConfirmDialog
import com.paulcoding.hviewer.ui.component.H7Tap
import com.paulcoding.hviewer.ui.component.HBackIcon
import com.paulcoding.hviewer.ui.component.HIcon
import com.paulcoding.hviewer.ui.component.HLoading
import com.paulcoding.hviewer.ui.page.AppViewModel

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -63,6 +72,8 @@ fun SettingsPage(
val window = (context as ComponentActivity).window
var lockModalVisible by remember { mutableStateOf(false) }
var appLockEnabled by remember { mutableStateOf(Preferences.pin.isNotEmpty()) }
var newVersion by remember { mutableStateOf("") }
var downloadUrl by remember { mutableStateOf("") }
val scrollState = rememberScrollState()

fun onAppLockEnabled(pin: String) {
Expand Down Expand Up @@ -147,8 +158,23 @@ fun SettingsPage(
}
}

H7Tap(modifier = Modifier.align(Alignment.CenterHorizontally)) {
appViewModel.setDevMode(it)
Row(
modifier = Modifier.align(Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically
) {
H7Tap() {
appViewModel.setDevMode(it)
}
HIcon(Icons.Outlined.Update, tint = MaterialTheme.colorScheme.primary) {
appViewModel.checkForUpdate(BuildConfig.VERSION_NAME,
onUpToDate = {
makeToast(R.string.up_to_date)
},
onUpdateAvailable = { version, url ->
newVersion = version
downloadUrl = url
})
}
}
}
}
Expand All @@ -169,6 +195,34 @@ fun SettingsPage(
if (lockModalVisible) LockModal(onDismiss = { lockModalVisible = false }) {
onAppLockEnabled(it)
}

ConfirmDialog(
showDialog = newVersion.isNotEmpty(),
title = stringResource(R.string.update_available),
text = newVersion,
confirmColor = MaterialTheme.colorScheme.primary,
confirmText = stringResource(R.string.install_now),
dismissColor = MaterialTheme.colorScheme.onBackground,
onDismiss = {
newVersion = ""
},
onConfirm = {
appViewModel.downloadAndInstallApk(context, downloadUrl)
newVersion = ""
}
)

if (appState.updatingApk) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.5f))
) {
Box(modifier = Modifier.align(Alignment.Center)) {
HLoading()
}
}
}
}

@Composable
Expand Down
Loading