Skip to content

Commit

Permalink
Error handling galore
Browse files Browse the repository at this point in the history
  • Loading branch information
pipe01 committed Apr 2, 2024
1 parent e1e41e5 commit 4c94417
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 67 deletions.
87 changes: 54 additions & 33 deletions app/src/main/java/net/pipe01/pinepartner/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,79 +1,101 @@
package net.pipe01.pinepartner

import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.navigation.compose.rememberNavController
import net.pipe01.pinepartner.data.AppDatabase
import net.pipe01.pinepartner.pages.ConnectingServicePage
import net.pipe01.pinepartner.scripting.BuiltInPlugins
import net.pipe01.pinepartner.service.BackgroundService
import net.pipe01.pinepartner.service.ServiceHandle
import net.pipe01.pinepartner.ui.theme.PinePartnerTheme
import net.pipe01.pinepartner.utils.composables.PluginsDisabledDialog

@SuppressLint("MissingPermission")
class MainActivity : ComponentActivity() {
private val serviceHandle = ServiceHandle(this)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val db = AppDatabase.create(applicationContext)

BuiltInPlugins.init(assets)

val intent = Intent(this, BackgroundService::class.java)

setContent {
val navController = rememberNavController()
val snackbarHostState = remember { SnackbarHostState() }

var service by remember { mutableStateOf<BackgroundService?>(null) }
var showBottomBar by remember { mutableStateOf(false) }

DisposableEffect(Unit) {
val conn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
service = (binder as BackgroundService.ServiceBinder).service
}

override fun onServiceDisconnected(name: ComponentName?) {
Log.d("MainActivity", "Service disconnected")
}
}

bindService(intent, conn, 0)
serviceHandle.start()

onDispose {
unbindService(conn)
serviceHandle.unbind()
}
}

PinePartnerTheme {
Scaffold(
bottomBar = {
if (showBottomBar) {
BottomBar(navController = navController)
if (serviceHandle.service == null) {
ConnectingServicePage(crashed = serviceHandle.hasCrashed)
} else {
var showPluginsDisabledDialog by remember { mutableStateOf(false) }

if (serviceHandle.pluginsDisabled) {
LaunchedEffect(Unit) {
val result = snackbarHostState.showSnackbar(
message = "Plugins have been disabled",
duration = SnackbarDuration.Long,
actionLabel = "Why?",
)
when (result) {
SnackbarResult.ActionPerformed -> {
showPluginsDisabledDialog = true
}
SnackbarResult.Dismissed -> {
}
}
}
}
) { padding ->
PermissionsFrame(
onGotAllPermissions = { showBottomBar = true },
) {
if (service != null) {

if (showPluginsDisabledDialog) {
PluginsDisabledDialog(
onDismissRequest = { showPluginsDisabledDialog = false }
)
}

Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
bottomBar = {
if (showBottomBar) {
BottomBar(navController = navController)
}
}
) { padding ->
PermissionsFrame(
onGotAllPermissions = { showBottomBar = true },
) {
NavFrame(
modifier = Modifier.padding(padding),
navController = navController,
backgroundService = service!!,
backgroundService = serviceHandle.service!!,
onShowBottomBar = { showBottomBar = it },
db = db,
)
Expand All @@ -87,7 +109,6 @@ class MainActivity : ComponentActivity() {
override fun onResume() {
super.onResume()

val intent = Intent(this, BackgroundService::class.java)
startForegroundService(intent)
serviceHandle.start()
}
}
3 changes: 2 additions & 1 deletion app/src/main/java/net/pipe01/pinepartner/NavFrame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import net.pipe01.pinepartner.pages.plugins.PluginsPage
import net.pipe01.pinepartner.pages.settings.NotificationSettingsPage
import net.pipe01.pinepartner.pages.settings.SettingsPage
import net.pipe01.pinepartner.service.BackgroundService
import net.pipe01.pinepartner.utils.PineError
import net.pipe01.pinepartner.utils.composables.ErrorDialog
import java.net.URLDecoder
import java.net.URLEncoder
Expand Down Expand Up @@ -132,7 +133,7 @@ fun NavFrame(
onShowBottomBar: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val errors = remember { mutableStateListOf<Error>() }
val errors = remember { mutableStateListOf<PineError>() }

if (errors.isNotEmpty()) {
val error = errors.first()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.pipe01.pinepartner.pages

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun ConnectingServicePage(crashed: Boolean) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.weight(1f))

CircularProgressIndicator()

Text(
modifier = Modifier.padding(top = 16.dp),
text = when (crashed) {
true -> "Background service crashed, restarting it"
false -> "Connecting to service"
}
)

Spacer(modifier = Modifier.weight(1f))
}
}

@Preview(showBackground = true, widthDp = 360, heightDp = 640)
@Composable
private fun ConnectingServicePagePreview() {
ConnectingServicePage(false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import kotlinx.coroutines.runBlocking
import net.pipe01.pinepartner.components.Header
import net.pipe01.pinepartner.service.BackgroundService
import net.pipe01.pinepartner.service.TransferProgress
import net.pipe01.pinepartner.utils.PineError
import net.pipe01.pinepartner.utils.composables.ErrorDialog
import net.pipe01.pinepartner.utils.toMinutesSeconds
import java.time.Duration
Expand All @@ -43,7 +44,7 @@ fun DFUPage(
) {
var uri by remember { mutableStateOf<Uri?>(null) }

var showErrorDialog by remember { mutableStateOf<Error?>(null) }
var showErrorDialog by remember { mutableStateOf<PineError?>(null) }

if (showErrorDialog != null) {
ErrorDialog(
Expand Down Expand Up @@ -105,7 +106,7 @@ private fun Uploader(
onStart: () -> Unit = { },
onFinish: () -> Unit = { },
onCancel: () -> Unit = { },
onError: (Error) -> Unit = { },
onError: (PineError) -> Unit = { },
) {
var progress by remember { mutableStateOf<TransferProgress?>(null) }

Expand All @@ -119,7 +120,7 @@ private fun Uploader(

launch {
backgroundService.startWatchDFU(jobId, address, uri).onFailure {
onError(Error("Failed to do DFU transfer", it))
onError(PineError("Failed to do DFU transfer", it))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import net.pipe01.pinepartner.data.AppDatabase
import net.pipe01.pinepartner.data.Watch
import net.pipe01.pinepartner.devices.WatchState
import net.pipe01.pinepartner.service.BackgroundService
import net.pipe01.pinepartner.utils.PineError

@SuppressLint("MissingPermission")
@Composable
Expand All @@ -35,7 +36,7 @@ fun DevicePage(
backgroundService: BackgroundService,
onUploadFirmware: () -> Unit,
onBrowseFiles: () -> Unit,
onError: (Error) -> Unit,
onError: (PineError) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()

Expand All @@ -50,12 +51,12 @@ fun DevicePage(
}

backgroundService.connectWatch(deviceAddress).onFailure {
onError(Error("Failed to connect to watch", it))
onError(PineError("Failed to connect to watch", it))
return@launch
}

state = backgroundService.getWatchState(deviceAddress).onFailure {
onError(Error("Failed to get watch state", it))
onError(PineError("Failed to get watch state", it))
}.getOrNull()
}
}
Expand All @@ -80,7 +81,7 @@ fun DevicePage(
Button(onClick = {
coroutineScope.launch {
backgroundService.sendTestNotification().onFailure {
onError(Error("Failed to send test notification", it))
onError(PineError("Failed to send test notification", it))
}
}
}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.pipe01.pinepartner.BuildConfig
import net.pipe01.pinepartner.components.Header
import net.pipe01.pinepartner.components.LoadingStandIn
import net.pipe01.pinepartner.data.AppDatabase
import net.pipe01.pinepartner.data.Watch
import net.pipe01.pinepartner.devices.Device
import net.pipe01.pinepartner.devices.WatchState
import net.pipe01.pinepartner.service.BackgroundService
import net.pipe01.pinepartner.utils.PineError
import net.pipe01.pinepartner.utils.composables.BoxWithFAB


Expand All @@ -54,7 +56,7 @@ fun DevicesPage(
backgroundService: BackgroundService,
onAddDevice: () -> Unit,
onDeviceClick: (address: String) -> Unit,
onError: (Error) -> Unit,
onError: (PineError) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()

Expand Down Expand Up @@ -114,6 +116,17 @@ fun DevicesPage(
)
}
}

if (BuildConfig.DEBUG) {
Button(onClick = {
CoroutineScope(Dispatchers.IO).launch {
backgroundService.crash()
Log.d("DevicesPage", "Crashed the service")
}
}) {
Text(text = "Crash")
}
}
}
}
}
Expand All @@ -127,7 +140,7 @@ private fun DeviceItem(
backgroundService: BackgroundService,
onClick: () -> Unit,
onRemoveDevice: () -> Unit,
onError: (Error) -> Unit,
onError: (PineError) -> Unit,
) {
val haptics = LocalHapticFeedback.current

Expand All @@ -139,7 +152,7 @@ private fun DeviceItem(
withContext(Dispatchers.Main) {
while (true) {
state = backgroundService.getWatchState(watch.address).onFailure {
onError(Error("Failed to get watch state", it))
onError(PineError("Failed to get watch state", it))
}.getOrNull()

delay(1000)
Expand Down Expand Up @@ -213,7 +226,7 @@ private fun DeviceItem(
Button(onClick = {
coroutineScope.launch {
backgroundService.disconnectWatch(watch.address).onFailure {
onError(Error("Failed to disconnect watch", it))
onError(PineError("Failed to disconnect watch", it))
}
}

Expand Down
Loading

0 comments on commit 4c94417

Please sign in to comment.