mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-19 07:15:25 +02:00
feat: Add custom notification support
This commit is contained in:
parent
81520c48ba
commit
37a8a9871e
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user