feat: Add custom notification support

This commit is contained in:
Myzel394 2023-10-24 12:47:46 +02:00
parent 81520c48ba
commit 37a8a9871e
No known key found for this signature in database
GPG Key ID: 50098FCA22080F0F
5 changed files with 105 additions and 15 deletions

View File

@ -8,7 +8,9 @@ import androidx.core.app.NotificationCompat
import app.myzel394.alibi.MainActivity import app.myzel394.alibi.MainActivity
import app.myzel394.alibi.NotificationHelper import app.myzel394.alibi.NotificationHelper
import app.myzel394.alibi.R import app.myzel394.alibi.R
import app.myzel394.alibi.db.NotificationSettings
import app.myzel394.alibi.enums.RecorderState import app.myzel394.alibi.enums.RecorderState
import kotlinx.serialization.Serializable
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.util.Calendar import java.util.Calendar
@ -18,12 +20,36 @@ data class RecorderNotificationHelper(
val context: Context, val context: Context,
val details: NotificationDetails? = null, val details: NotificationDetails? = null,
) { ) {
@Serializable
data class NotificationDetails( data class NotificationDetails(
val title: String, val title: String,
val description: String, val description: String,
val icon: Int, val icon: Int,
val isOngoing: Boolean, val isOngoing: Boolean,
) {
companion object {
fun fromNotificationSettings(
context: Context,
settings: NotificationSettings,
): NotificationDetails {
return if (settings.preset == null) {
NotificationDetails(
settings.title,
settings.message,
settings.iconID,
settings.showOngoing,
) )
} else {
NotificationDetails(
context.getString(settings.preset.titleID),
context.getString(settings.preset.messageID),
settings.preset.iconID,
settings.preset.showOngoing,
)
}
}
}
}
private fun getNotificationChangeStateIntent( private fun getNotificationChangeStateIntent(
newState: RecorderState, newState: RecorderState,

View File

@ -14,6 +14,7 @@ import app.myzel394.alibi.NotificationHelper
import app.myzel394.alibi.R import app.myzel394.alibi.R
import app.myzel394.alibi.enums.RecorderState import app.myzel394.alibi.enums.RecorderState
import app.myzel394.alibi.ui.utils.PermissionHelper import app.myzel394.alibi.ui.utils.PermissionHelper
import kotlinx.serialization.json.Json
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.util.Calendar import java.util.Calendar
@ -51,6 +52,15 @@ abstract class RecorderService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) { when (intent?.action) {
"init" -> {
notificationDetails = intent.getStringExtra("notificationDetails")?.let {
Json.decodeFromString(
RecorderNotificationHelper.NotificationDetails.serializer(),
it
)
}
}
"changeState" -> { "changeState" -> {
val newState = intent.getStringExtra("newState")?.let { val newState = intent.getStringExtra("newState")?.let {
RecorderState.valueOf(it) RecorderState.valueOf(it)

View File

@ -20,8 +20,15 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberStandardBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -31,22 +38,51 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.myzel394.alibi.NotificationHelper
import app.myzel394.alibi.R import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.services.RecorderNotificationHelper
import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
import app.myzel394.alibi.ui.components.atoms.PermissionRequester import app.myzel394.alibi.ui.components.atoms.PermissionRequester
import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.utils.rememberFileSaverDialog import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.lastOrNull
import kotlinx.coroutines.launch
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle import java.time.format.FormatStyle
@Composable @Composable
fun StartRecording( fun StartRecording(
audioRecorder: AudioRecorderModel, audioRecorder: AudioRecorderModel,
// Loading this from parent, because if we load it ourselves
// and permissions have already been granted, initial
// settings will be used, instead of the actual settings.
appSettings: AppSettings
) { ) {
val context = LocalContext.current val context = LocalContext.current
// We can't get the current `notificationDetails` inside the
// `onPermissionAvailable` function. We'll instead use this hack
// with `LaunchedEffect` to get the current value.
var startRecording by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(startRecording) {
if (startRecording) {
startRecording = false
audioRecorder.notificationDetails = appSettings.notificationSettings.let {
if (it == null)
null
else
RecorderNotificationHelper.NotificationDetails.fromNotificationSettings(
context,
it
)
}
audioRecorder.startRecording(context)
}
}
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween, verticalArrangement = Arrangement.SpaceBetween,
@ -57,14 +93,12 @@ fun StartRecording(
permission = Manifest.permission.RECORD_AUDIO, permission = Manifest.permission.RECORD_AUDIO,
icon = Icons.Default.Mic, icon = Icons.Default.Mic,
onPermissionAvailable = { onPermissionAvailable = {
audioRecorder.startRecording(context) startRecording = true
}, },
) { trigger -> ) { trigger ->
val label = stringResource(R.string.ui_audioRecorder_action_start_label) val label = stringResource(R.string.ui_audioRecorder_action_start_label)
Button( Button(
onClick = { onClick = trigger,
trigger()
},
modifier = Modifier modifier = Modifier
.semantics { .semantics {
contentDescription = label contentDescription = label
@ -98,7 +132,10 @@ fun StartRecording(
.value .value
Text( Text(
stringResource(R.string.ui_audioRecorder_action_start_description, settings.audioRecorderSettings.maxDuration / 1000 / 60), stringResource(
R.string.ui_audioRecorder_action_start_description,
settings.audioRecorderSettings.maxDuration / 1000 / 60
),
style = MaterialTheme.typography.bodySmall.copy( style = MaterialTheme.typography.bodySmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
), ),
@ -115,7 +152,8 @@ fun StartRecording(
) { ) {
val label = stringResource( val label = stringResource(
R.string.ui_audioRecorder_action_saveOldRecording_label, R.string.ui_audioRecorder_action_saveOldRecording_label,
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(audioRecorder.lastRecording!!.recordingStart), DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)
.format(audioRecorder.lastRecording!!.recordingStart),
) )
Button( Button(
modifier = Modifier modifier = Modifier
@ -140,8 +178,7 @@ fun StartRecording(
Text(label) Text(label)
} }
} }
} } else
else
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
} }
} }

View File

@ -13,10 +13,14 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.LastRecording import app.myzel394.alibi.db.LastRecording
import app.myzel394.alibi.enums.RecorderState import app.myzel394.alibi.enums.RecorderState
import app.myzel394.alibi.services.AudioRecorderService import app.myzel394.alibi.services.AudioRecorderService
import app.myzel394.alibi.services.RecorderNotificationHelper
import app.myzel394.alibi.services.RecorderService import app.myzel394.alibi.services.RecorderService
import kotlinx.coroutines.flow.last
import kotlinx.serialization.json.Json
class AudioRecorderModel : ViewModel() { class AudioRecorderModel : ViewModel() {
var recorderState by mutableStateOf(RecorderState.IDLE) var recorderState by mutableStateOf(RecorderState.IDLE)
@ -45,6 +49,7 @@ class AudioRecorderModel : ViewModel() {
var onRecordingSave: () -> Unit = {} var onRecordingSave: () -> Unit = {}
var onError: () -> Unit = {} var onError: () -> Unit = {}
var notificationDetails: RecorderNotificationHelper.NotificationDetails? = null
private val connection = object : ServiceConnection { private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
@ -90,7 +95,19 @@ class AudioRecorderModel : ViewModel() {
context.unbindService(connection) context.unbindService(connection)
} }
val intent = Intent(context, AudioRecorderService::class.java) val intent = Intent(context, AudioRecorderService::class.java).apply {
action = "init"
if (notificationDetails != null) {
putExtra(
"notificationDetails",
Json.encodeToString(
RecorderNotificationHelper.NotificationDetails.serializer(),
notificationDetails!!,
),
)
}
}
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
context.bindService(intent, connection, Context.BIND_AUTO_CREATE) context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
} }
@ -118,10 +135,6 @@ class AudioRecorderModel : ViewModel() {
recorderService!!.changeState(RecorderState.RECORDING) recorderService!!.changeState(RecorderState.RECORDING)
} }
fun setNotificationDetails(details: RecorderService.NotificationDetails) {
recorderService?.notificationDetails = details
}
fun setMaxAmplitudesAmount(amount: Int) { fun setMaxAmplitudesAmount(amount: Int) {
recorderService?.amplitudesAmount = amount recorderService?.amplitudesAmount = amount
} }

View File

@ -38,6 +38,7 @@ import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.db.LastRecording import app.myzel394.alibi.db.LastRecording
import app.myzel394.alibi.services.RecorderNotificationHelper
import app.myzel394.alibi.ui.effects.rememberSettings import app.myzel394.alibi.ui.effects.rememberSettings
import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.AudioRecorderModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -171,10 +172,13 @@ fun AudioRecorder(
.fillMaxSize() .fillMaxSize()
.padding(padding), .padding(padding),
) { ) {
val appSettings =
context.dataStore.data.collectAsState(AppSettings.getDefaultInstance()).value
if (audioRecorder.isInRecording) if (audioRecorder.isInRecording)
RecordingStatus(audioRecorder = audioRecorder) RecordingStatus(audioRecorder = audioRecorder)
else else
StartRecording(audioRecorder = audioRecorder) StartRecording(audioRecorder = audioRecorder, appSettings = appSettings)
} }
} }
} }