Upload via GUI (4909 Dateien)
This commit is contained in:
@@ -5,6 +5,8 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -22,6 +24,14 @@ import de.deskhub.staff.ui.DeskHubViewModel
|
||||
import de.deskhub.staff.ui.DeskHubViewModelFactory
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val connectionPingHandler = Handler(Looper.getMainLooper())
|
||||
private val connectionPing = object : Runnable {
|
||||
override fun run() {
|
||||
AppConnectionPing.send(applicationContext)
|
||||
connectionPingHandler.postDelayed(this, 4 * 60 * 1000L)
|
||||
}
|
||||
}
|
||||
|
||||
private val viewModel: DeskHubViewModel by viewModels {
|
||||
DeskHubViewModelFactory(TicketRepository(SecureSessionStore(applicationContext)))
|
||||
}
|
||||
@@ -35,9 +45,16 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
connectionPingHandler.removeCallbacks(connectionPing)
|
||||
connectionPing.run()
|
||||
DeskHubWidget.requestUpdate(applicationContext)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
connectionPingHandler.removeCallbacks(connectionPing)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
de.deskhub.staff.ui.I18n.init(applicationContext)
|
||||
@@ -48,9 +65,12 @@ class MainActivity : ComponentActivity() {
|
||||
// Nach der Anmeldung: um Erlaubnis für Benachrichtigungen bitten (einmal) und das Gerät für Push anmelden
|
||||
LaunchedEffect(signedIn) {
|
||||
if (signedIn) {
|
||||
AppConnectionPing.scheduleBackground(applicationContext)
|
||||
requestNotificationPermissionOnce()
|
||||
PushRegistration.registerCurrent(applicationContext)
|
||||
CrashReporter.sendPending(applicationContext)
|
||||
} else {
|
||||
AppConnectionPing.cancelBackground(applicationContext)
|
||||
}
|
||||
}
|
||||
DeskHubApp(viewModel)
|
||||
@@ -86,4 +106,4 @@ class MainActivity : ComponentActivity() {
|
||||
prefs.edit().putBoolean("asked_notifications", true).apply()
|
||||
notificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ class TicketRepository(
|
||||
email = email.trim(),
|
||||
password = password,
|
||||
totpCode = totpCode?.takeIf { it.isNotBlank() },
|
||||
deviceName = "${Build.MANUFACTURER} ${Build.MODEL}".trim(),
|
||||
deviceName = "Android (${Build.MANUFACTURER} ${Build.MODEL})".trim(),
|
||||
),
|
||||
)
|
||||
if (response.requiresTotp) return LoginResult.TotpRequired
|
||||
@@ -319,6 +319,11 @@ class TicketRepository(
|
||||
return requireApi().createTicket(request).ticket.toDomain()
|
||||
}
|
||||
|
||||
suspend fun appUpdate() = requireApi().appUpdate()
|
||||
|
||||
suspend fun downloadAppUpdate(target: java.io.File, sha256: String, onProgress: (Int) -> Unit) =
|
||||
requireApi().downloadAppUpdate(target, sha256, onProgress)
|
||||
|
||||
suspend fun activitySince(since: String): ActivityUpdate {
|
||||
val response = requireApi().activity(since)
|
||||
return ActivityUpdate(response.newReplies, response.newUnassigned, response.serverTime, response.health.map { HealthIssue(it.level, it.message) })
|
||||
|
||||
@@ -267,6 +267,7 @@ private fun StaffNavigation(state: DeskHubState, viewModel: DeskHubViewModel) {
|
||||
) { outerPadding ->
|
||||
Column(Modifier.padding(outerPadding)) {
|
||||
StatusBanners(state, onRetry = viewModel::retryConnection)
|
||||
UpdateBanner(state, viewModel)
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "dashboard",
|
||||
@@ -652,6 +653,69 @@ internal fun StatusBanners(state: DeskHubState, onRetry: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Hinweis „Update verfügbar“ mit Download und anschließender Installation (Android fragt dafür einmal nach der Erlaubnis). */
|
||||
@Composable
|
||||
internal fun UpdateBanner(state: DeskHubState, viewModel: DeskHubViewModel) {
|
||||
val context = LocalContext.current
|
||||
val current = remember(context) { runCatching { context.packageManager.getPackageInfo(context.packageName, 0).versionName }.getOrNull().orEmpty() }
|
||||
// Aus dem Play Store installierte Apps aktualisiert der Store selbst – Selbst-Update nur für direkt installierte APKs
|
||||
val fromPlayStore = remember(context) {
|
||||
runCatching {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 30) context.packageManager.getInstallSourceInfo(context.packageName).installingPackageName
|
||||
else @Suppress("DEPRECATION") context.packageManager.getInstallerPackageName(context.packageName)
|
||||
}.getOrNull() == "com.android.vending"
|
||||
}
|
||||
LaunchedEffect(state.staff?.id) {
|
||||
if (state.staff != null && current.isNotBlank() && !fromPlayStore) {
|
||||
while (true) {
|
||||
viewModel.checkAppUpdate(current)
|
||||
kotlinx.coroutines.delay(6 * 3600 * 1000L)
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(state.appUpdateFile) {
|
||||
val file = state.appUpdateFile ?: return@LaunchedEffect
|
||||
viewModel.appUpdateFileHandled()
|
||||
if (android.os.Build.VERSION.SDK_INT >= 26 && !context.packageManager.canRequestPackageInstalls()) {
|
||||
// Einmalige Erlaubnis „Aus dieser Quelle installieren“ – danach erneut auf „Jetzt aktualisieren“ tippen
|
||||
context.startActivity(
|
||||
android.content.Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, android.net.Uri.parse("package:${context.packageName}"))
|
||||
.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
)
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val uri = androidx.core.content.FileProvider.getUriForFile(context, "${context.packageName}.updates", file)
|
||||
context.startActivity(
|
||||
android.content.Intent(android.content.Intent.ACTION_VIEW)
|
||||
.setDataAndType(uri, "application/vnd.android.package-archive")
|
||||
.addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION or android.content.Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
)
|
||||
}
|
||||
val info = state.appUpdate ?: return
|
||||
val firstBanner = !state.offline && state.healthIssues.isEmpty() // nur das oberste Banner braucht Abstand zur Statusleiste
|
||||
Surface(color = MaterialTheme.colorScheme.primaryContainer, modifier = Modifier.fillMaxWidth()) {
|
||||
Column(Modifier.then(if (firstBanner) Modifier.statusBarsPadding() else Modifier).padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 4.dp)) {
|
||||
Text(
|
||||
state.appUpdateProgress?.let { "Update ${info.version} wird geladen … $it %" }
|
||||
?: ("Update verfügbar: DeskHub ${info.version} (installiert: $current)" + if (info.notes.isNotBlank()) "\n${info.notes}" else ""),
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
state.appUpdateProgress?.let {
|
||||
androidx.compose.material3.LinearProgressIndicator(progress = { it / 100f }, modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 6.dp, end = 8.dp))
|
||||
}
|
||||
state.appUpdateError?.let { Text(it, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 4.dp)) }
|
||||
if (state.appUpdateProgress == null) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
TextButton(onClick = viewModel::dismissAppUpdate) { Text("Später") }
|
||||
TextButton(onClick = { viewModel.downloadAppUpdate(context.cacheDir) }) { Text("Jetzt aktualisieren") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun androidx.navigation.NavController.navigateToTab(route: String) {
|
||||
navigate(route) {
|
||||
popUpTo(graph.findStartDestination().id) { saveState = true }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="updates" path="updates/" />
|
||||
</paths>
|
||||
Reference in New Issue
Block a user