From 0bfed1831473c4e8a83c040302d61b800fa1b113 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Aug 2023 19:07:54 +0200 Subject: [PATCH] refactor: Migrate foreground services to new architecture --- app/src/main/AndroidManifest.xml | 2 +- .../alibi/services/AudioRecorderService.kt | 20 +++++--- .../ExtraRecorderInformationService.kt | 25 ++++------ .../alibi/services/IntervalRecorderService.kt | 49 ++++++++++++++++--- .../alibi/services/RecorderService.kt | 24 +++++---- .../java/app/myzel394/alibi/ui/Navigation.kt | 18 +++++-- .../atoms/RealTimeAudioVisualizer.kt | 9 ++-- .../atoms/SaveRecordingButton.kt | 4 -- .../molecules/RecordingStatus.kt | 23 +++------ .../AudioRecorder/molecules/StartRecording.kt | 29 ++--------- .../alibi/ui/models/AudioRecorderModel.kt | 48 +++++++++++++++--- .../alibi/ui/screens/AudioRecorder.kt | 27 +++------- .../alibi/ui/screens/SettingsScreen.kt | 9 ++-- 13 files changed, 164 insertions(+), 123 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b8f2dbc..fccf7db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,7 +37,7 @@ - + \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt index 273431d..02d2bce 100644 --- a/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt @@ -4,11 +4,13 @@ import android.media.MediaRecorder import android.os.Build class AudioRecorderService: IntervalRecorderService() { + var amplitudesAmount = 1000 + var recorder: MediaRecorder? = null private set val filePath: String - get() = "$folder/$counter.${settings.fileExtension}" + get() = "$folder/$counter.${settings!!.fileExtension}" private fun createRecorder(): MediaRecorder { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -18,10 +20,10 @@ class AudioRecorderService: IntervalRecorderService() { }.apply { setAudioSource(MediaRecorder.AudioSource.MIC) setOutputFile(filePath) - setOutputFormat(settings.outputFormat) - setAudioEncoder(settings.encoder) - setAudioEncodingBitRate(settings.bitRate) - setAudioSamplingRate(settings.samplingRate) + setOutputFormat(settings!!.outputFormat) + setAudioEncoder(settings!!.encoder) + setAudioEncodingBitRate(settings!!.bitRate) + setAudioSamplingRate(settings!!.samplingRate) } } @@ -47,9 +49,13 @@ class AudioRecorderService: IntervalRecorderService() { recorder = newRecorder } - override fun getAmplitudeAmount(): Int { - return 100 + override fun stop() { + super.stop() + + resetRecorder() } + override fun getAmplitudeAmount(): Int = amplitudesAmount + override fun getAmplitude(): Int = recorder?.maxAmplitude ?: 0 } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt b/app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt index 784ea5f..2d70e54 100644 --- a/app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt @@ -2,6 +2,7 @@ package app.myzel394.alibi.services import android.os.Handler import android.os.Looper +import app.myzel394.alibi.enums.RecorderState import java.util.Timer import java.util.TimerTask import java.util.concurrent.Executors @@ -12,12 +13,12 @@ abstract class ExtraRecorderInformationService: RecorderService() { abstract fun getAmplitudeAmount(): Int abstract fun getAmplitude(): Int - private var recordingTime = 0L + var recordingTime = 0L + private set private lateinit var recordingTimeTimer: ScheduledExecutorService var amplitudes = mutableListOf() private set - private lateinit var amplitudesTimer: Timer private val handler = Handler(Looper.getMainLooper()) @@ -29,6 +30,7 @@ abstract class ExtraRecorderInformationService: RecorderService() { it.scheduleAtFixedRate( { recordingTime += 1000 + onRecordingTimeChange?.invoke(recordingTime) }, 0, 1000, @@ -38,7 +40,12 @@ abstract class ExtraRecorderInformationService: RecorderService() { } private fun updateAmplitude() { + if (state !== RecorderState.RECORDING) { + return + } + amplitudes.add(getAmplitude()) + onAmplitudeChange?.invoke(amplitudes) // Delete old amplitudes if (amplitudes.size > getAmplitudeAmount()) { @@ -49,17 +56,7 @@ abstract class ExtraRecorderInformationService: RecorderService() { } private fun createAmplitudesTimer() { - amplitudesTimer = Timer().also { - it.scheduleAtFixedRate( - object: TimerTask() { - override fun run() { - updateAmplitude() - } - }, - 0, - 100, - ) - } + handler.postDelayed(::updateAmplitude, 100) } override fun start() { @@ -69,7 +66,6 @@ abstract class ExtraRecorderInformationService: RecorderService() { override fun pause() { recordingTimeTimer.shutdown() - amplitudesTimer.cancel() } override fun resume() { @@ -79,7 +75,6 @@ abstract class ExtraRecorderInformationService: RecorderService() { override fun stop() { recordingTimeTimer.shutdown() - amplitudesTimer.cancel() } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt index cdb0063..1004adf 100644 --- a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt @@ -1,21 +1,33 @@ package app.myzel394.alibi.services +import android.content.Context import android.media.MediaRecorder +import app.myzel394.alibi.dataStore import app.myzel394.alibi.db.AudioRecorderSettings import app.myzel394.alibi.db.LastRecording +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import java.io.File +import java.time.LocalDateTime import java.util.Timer import java.util.TimerTask +import java.util.UUID import java.util.concurrent.Executor import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit abstract class IntervalRecorderService: ExtraRecorderInformationService() { + private var job = SupervisorJob() + private var scope = CoroutineScope(Dispatchers.IO + job) + protected var counter = 0 private set protected lateinit var folder: File - lateinit var settings: Settings + var settings: Settings? = null protected set private lateinit var cycleTimer: ScheduledExecutorService @@ -23,10 +35,10 @@ abstract class IntervalRecorderService: ExtraRecorderInformationService() { fun createLastRecording(): LastRecording = LastRecording( folderPath = folder.absolutePath, recordingStart = recordingStart, - maxDuration = settings.maxDuration, - fileExtension = settings.fileExtension, - intervalDuration = settings.intervalDuration, - forceExactMaxDuration = settings.forceExactMaxDuration, + maxDuration = settings!!.maxDuration, + fileExtension = settings!!.fileExtension, + intervalDuration = settings!!.intervalDuration, + forceExactMaxDuration = settings!!.forceExactMaxDuration, ) // Make overrideable @@ -41,32 +53,53 @@ abstract class IntervalRecorderService: ExtraRecorderInformationService() { startNewCycle() }, 0, - settings.intervalDuration, + settings!!.intervalDuration, TimeUnit.MILLISECONDS ) } } + private fun getRandomFileFolder(): String { + // uuid + val folder = UUID.randomUUID().toString() + + return "${externalCacheDir!!.absolutePath}/$folder" + } + override fun start() { + super.start() + + folder = File(getRandomFileFolder()) folder.mkdirs() - createTimer() + scope.launch { + dataStore.data.collectLatest { preferenceSettings -> + if (settings == null) { + settings = Settings.from(preferenceSettings.audioRecorderSettings) + + createTimer() + } + } + } } override fun pause() { + super.pause() cycleTimer.shutdown() } override fun resume() { + super.resume() createTimer() } override fun stop() { + super.stop() cycleTimer.shutdown() } private fun deleteOldRecordings() { - val timeMultiplier = settings.maxDuration / settings.intervalDuration + val timeMultiplier = settings!!.maxDuration / settings!!.intervalDuration val earliestCounter = counter - timeMultiplier folder.listFiles()?.forEach { file -> diff --git a/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt index f881948..86b800b 100644 --- a/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt @@ -26,7 +26,7 @@ abstract class RecorderService: Service() { lateinit var recordingStart: LocalDateTime private set - var state = RecorderState.RECORDING + var state = RecorderState.IDLE private set var onStateChange: ((RecorderState) -> Unit)? = null @@ -60,28 +60,34 @@ abstract class RecorderService: Service() { pause() isPaused = true } - else -> throw IllegalStateException("$newState is not a valid state. Destroy or recreate the service instead.") + RecorderState.IDLE -> stop() } state = newState onStateChange?.invoke(newState) } + // Must be called immediately after the service is created + fun startRecording() { + recordingStart = LocalDateTime.now() + + val notification = buildNotification() + startForeground(NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID, notification) + + // Start + changeState(RecorderState.RECORDING) + } + override fun onCreate() { super.onCreate() - val notification = buildNotification() - - startForeground(NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID, notification) - - recordingStart = LocalDateTime.now() - start() + startRecording() } override fun onDestroy() { super.onDestroy() - stop() + changeState(RecorderState.IDLE) stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() diff --git a/app/src/main/java/app/myzel394/alibi/ui/Navigation.kt b/app/src/main/java/app/myzel394/alibi/ui/Navigation.kt index a88aa9b..7dc6ffb 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/Navigation.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/Navigation.kt @@ -11,11 +11,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import app.myzel394.alibi.dataStore import app.myzel394.alibi.ui.enums.Screen +import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.screens.AudioRecorder import app.myzel394.alibi.ui.screens.SettingsScreen import app.myzel394.alibi.ui.screens.WelcomeScreen @@ -23,7 +25,9 @@ import app.myzel394.alibi.ui.screens.WelcomeScreen const val SCALE_IN = 1.25f @Composable -fun Navigation() { +fun Navigation( + audioRecorder: AudioRecorderModel = viewModel() +) { val navController = rememberNavController() val context = LocalContext.current val settings = context @@ -32,6 +36,8 @@ fun Navigation() { .collectAsState(initial = null) .value ?: return + audioRecorder.BindToService(context) + NavHost( modifier = Modifier .background(MaterialTheme.colorScheme.background), @@ -53,7 +59,10 @@ fun Navigation() { scaleOut(targetScale = SCALE_IN) + fadeOut(tween(durationMillis = 150)) } ) { - AudioRecorder(navController = navController) + AudioRecorder( + navController = navController, + audioRecorder = audioRecorder, + ) } composable( Screen.Settings.route, @@ -64,7 +73,10 @@ fun Navigation() { scaleOut(targetScale = 1 / SCALE_IN) + fadeOut(tween(durationMillis = 150)) } ) { - SettingsScreen(navController = navController) + SettingsScreen( + navController = navController, + audioRecorder = audioRecorder, + ) } } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/RealTimeAudioVisualizer.kt b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/RealTimeAudioVisualizer.kt index aeeaa86..7d35b35 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/RealTimeAudioVisualizer.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/RealTimeAudioVisualizer.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import app.myzel394.alibi.services.RecorderService import app.myzel394.alibi.ui.MAX_AMPLITUDE +import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.utils.clamp import kotlinx.coroutines.launch import kotlin.math.ceil @@ -34,10 +35,10 @@ private const val GROW_END = BOX_DIFF * 4 @Composable fun RealtimeAudioVisualizer( - service: RecorderService, + audioRecorder: AudioRecorderModel, ) { val scope = rememberCoroutineScope() - val amplitudes = service.amplitudes + val amplitudes = audioRecorder.amplitudes!! val primary = MaterialTheme.colorScheme.primary val primaryMuted = primary.copy(alpha = 0.3f) @@ -47,7 +48,7 @@ fun RealtimeAudioVisualizer( val animationProgress = remember { Animatable(0f) } LaunchedEffect(Unit) { - service.setOnAmplitudeUpdateListener { + audioRecorder.onAmplitudeChange = { scope.launch { animationProgress.snapTo(0f) animationProgress.animateTo( @@ -66,7 +67,7 @@ fun RealtimeAudioVisualizer( LaunchedEffect(screenWidth) { // Add 1 to allow the visualizer to overflow the screen - service.maxAmplitudes = ceil(screenWidth.toInt() / BOX_DIFF).toInt() + 1 + audioRecorder.setMaxAmplitudesAmount(ceil(screenWidth.toInt() / BOX_DIFF).toInt() + 1) } Canvas( diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/SaveRecordingButton.kt b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/SaveRecordingButton.kt index 550e2e4..1933c68 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/SaveRecordingButton.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/SaveRecordingButton.kt @@ -88,13 +88,9 @@ fun SaveRecordingButton( .then(modifier), onClick = { isProcessingAudio = true - RecorderService.stopService(context) scope.launch { try { - val file = service.concatenateFiles() - - onSaveFile(file) } catch (error: Exception) { Log.getStackTraceString(error) } finally { diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/RecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/RecordingStatus.kt index 2648d8f..19aa303 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/RecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/RecordingStatus.kt @@ -47,6 +47,7 @@ import app.myzel394.alibi.ui.components.AudioRecorder.atoms.ConfirmDeletionDialo import app.myzel394.alibi.ui.components.AudioRecorder.atoms.RealtimeAudioVisualizer import app.myzel394.alibi.ui.components.AudioRecorder.atoms.SaveRecordingButton import app.myzel394.alibi.ui.components.atoms.Pulsating +import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.utils.KeepScreenOn import app.myzel394.alibi.ui.utils.formatDuration import kotlinx.coroutines.delay @@ -57,15 +58,12 @@ import java.time.ZoneId @Composable fun RecordingStatus( - service: RecorderService, - onSaveFile: (File) -> Unit, + audioRecorder: AudioRecorderModel, ) { val context = LocalContext.current var now by remember { mutableStateOf(LocalDateTime.now()) } - val progress = service.recordingTime.value!! / (service.settings!!.maxDuration / 1000f) - LaunchedEffect(Unit) { while (true) { now = LocalDateTime.now() @@ -74,7 +72,7 @@ fun RecordingStatus( } // Only show animation when the recording has just started - val recordingJustStarted = service.recordingTime.value!! < 1 + val recordingJustStarted = audioRecorder.recordingTime!! <= 1000L var progressVisible by remember { mutableStateOf(!recordingJustStarted) } LaunchedEffect(Unit) { progressVisible = true @@ -89,7 +87,7 @@ fun RecordingStatus( verticalArrangement = Arrangement.SpaceBetween, ) { Box {} - RealtimeAudioVisualizer(service = service) + RealtimeAudioVisualizer(audioRecorder = audioRecorder) Column( horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -97,7 +95,7 @@ fun RecordingStatus( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, ) { - val distance = Duration.between(service.recordingStart!!, now).toMillis() + val distance = Duration.between(audioRecorder.recorderService!!.recordingStart, now).toMillis() Pulsating { Box( @@ -121,7 +119,7 @@ fun RecordingStatus( ) ) { LinearProgressIndicator( - progress = progress, + progress = audioRecorder.progress, modifier = Modifier .width(300.dp) ) @@ -137,8 +135,7 @@ fun RecordingStatus( }, onConfirm = { showDeleteDialog = false - RecorderService.stopService(context) - service.reset() + audioRecorder.stopRecording(context) }, ) } @@ -163,11 +160,5 @@ fun RecordingStatus( } } val alpha by animateFloatAsState(if (progressVisible) 1f else 0f, tween(1000)) - SaveRecordingButton( - modifier = Modifier - .alpha(alpha), - service = service, - onSaveFile = onSaveFile, - ) } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/StartRecording.kt b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/StartRecording.kt index fd5446b..b8cd59d 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/StartRecording.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/molecules/StartRecording.kt @@ -34,6 +34,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.autoSaver import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -52,15 +53,14 @@ import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE import app.myzel394.alibi.ui.components.AudioRecorder.atoms.AudioVisualizer import app.myzel394.alibi.ui.components.AudioRecorder.atoms.SaveRecordingButton import app.myzel394.alibi.ui.components.atoms.PermissionRequester +import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.utils.rememberFileSaverDialog import java.time.format.DateTimeFormatter import java.time.format.FormatStyle @Composable fun StartRecording( - connection: ServiceConnection, - service: RecorderService? = null, - onStart: () -> Unit, + audioRecorder: AudioRecorderModel, ) { val context = LocalContext.current val saveFile = rememberFileSaverDialog("audio/*") @@ -80,17 +80,7 @@ fun StartRecording( ) }, onPermissionAvailable = { - RecorderService.startService(context, connection) - - if (service == null) { - onStart() - } else { - // To avoid any leaks from the previous recording, we need to wait until it - // fully started - service.setOnStartedListener { - onStart() - } - } + audioRecorder.startRecording(context) }, ) { trigger -> val label = stringResource(R.string.ui_audioRecorder_action_start_label) @@ -140,21 +130,12 @@ fun StartRecording( .fillMaxWidth(), textAlign = TextAlign.Center, ) - if (service?.hasRecordingAvailable == true) + if (false) Column( modifier = Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Bottom, ) { - SaveRecordingButton( - service = service, - onSaveFile = saveFile, - label = stringResource( - R.string.ui_audioRecorder_action_saveOldRecording_label, - DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(service.recordingStart), - ), - - ) } else Spacer(modifier = Modifier.weight(1f)) diff --git a/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt b/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt index f5ca99a..17c3763 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt @@ -5,6 +5,8 @@ import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.IBinder +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -19,11 +21,20 @@ class AudioRecorderModel: ViewModel() { private set var recordingTime by mutableStateOf(null) private set - var amplitudes by mutableStateOf?>(null) + var amplitudes by mutableStateOf>(emptyList()) private set + var onAmplitudeChange: () -> Unit = {} + + val isInRecording: Boolean + get() = recorderState !== RecorderState.IDLE && recordingTime != null + + val progress: Float + get() = (recordingTime!! / recorderService!!.settings!!.maxDuration).toFloat() + private var intent: Intent? = null - private var recorderService: RecorderService? = null + var recorderService: AudioRecorderService? = null + private set private val connection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { @@ -36,8 +47,12 @@ class AudioRecorderModel: ViewModel() { } recorder.onAmplitudeChange = { amps -> amplitudes = amps + onAmplitudeChange() } } + recorderState = recorderService!!.state + recordingTime = recorderService!!.recordingTime + amplitudes = recorderService!!.amplitudes } override fun onServiceDisconnected(arg0: ComponentName) { @@ -49,7 +64,7 @@ class AudioRecorderModel: ViewModel() { fun reset() { recorderState = RecorderState.IDLE recordingTime = null - amplitudes = null + amplitudes = emptyList() } fun startRecording(context: Context) { @@ -57,13 +72,30 @@ class AudioRecorderModel: ViewModel() { context.unbindService(connection) } - val intent = Intent(context, AudioRecorderService::class.java) - ContextCompat.startForegroundService(context, intent) - context.bindService(intent, connection, Context.BIND_AUTO_CREATE) + intent = Intent(context, AudioRecorderService::class.java) + ContextCompat.startForegroundService(context, intent!!) + context.bindService(intent!!, connection, Context.BIND_AUTO_CREATE) } fun stopRecording(context: Context) { - context.stopService(intent) - context.unbindService(connection) + runCatching { + context.unbindService(connection) + context.stopService(intent) + } + + reset() + } + + fun setMaxAmplitudesAmount(amount: Int) { + recorderService?.amplitudesAmount = amount + } + + @Composable + fun BindToService(context: Context) { + LaunchedEffect(Unit) { + Intent(context, AudioRecorderService::class.java).also { intent -> + context.bindService(intent, connection, 0) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt index c5877f4..3736f0c 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt @@ -14,26 +14,25 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController -import app.myzel394.alibi.services.bindToRecorderService import app.myzel394.alibi.ui.components.AudioRecorder.molecules.RecordingStatus import app.myzel394.alibi.ui.components.AudioRecorder.molecules.StartRecording import app.myzel394.alibi.ui.enums.Screen import app.myzel394.alibi.ui.utils.rememberFileSaverDialog import app.myzel394.alibi.R +import app.myzel394.alibi.ui.models.AudioRecorderModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun AudioRecorder( navController: NavController, + audioRecorder: AudioRecorderModel ) { + val context = LocalContext.current val saveFile = rememberFileSaverDialog("audio/aac") - val (connection, service) = bindToRecorderService() - - var showRecorderStatus by remember { - mutableStateOf(service?.isRecording ?: false) - } Scaffold( topBar = { @@ -61,22 +60,12 @@ fun AudioRecorder( .fillMaxSize() .padding(padding), ) { - if (showRecorderStatus && service?.recordingTime?.value != null) + if (audioRecorder.isInRecording) RecordingStatus( - service = service, - onSaveFile = { - saveFile(it) - showRecorderStatus = false - } + audioRecorder = audioRecorder, ) else - StartRecording( - connection = connection, - service = service, - onStart = { - showRecorderStatus = true - } - ) + StartRecording(audioRecorder = audioRecorder) } } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt index f47ed32..2a92a54 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt @@ -32,7 +32,6 @@ import androidx.navigation.NavController import app.myzel394.alibi.R import app.myzel394.alibi.dataStore import app.myzel394.alibi.db.AppSettings -import app.myzel394.alibi.services.bindToRecorderService import app.myzel394.alibi.ui.components.SettingsScreen.atoms.BitrateTile import app.myzel394.alibi.ui.components.SettingsScreen.atoms.EncoderTile import app.myzel394.alibi.ui.components.SettingsScreen.atoms.ForceExactMaxDurationTile @@ -43,15 +42,15 @@ import app.myzel394.alibi.ui.components.SettingsScreen.atoms.SamplingRateTile import app.myzel394.alibi.ui.components.atoms.GlobalSwitch import app.myzel394.alibi.ui.components.atoms.MessageBox import app.myzel394.alibi.ui.components.atoms.MessageType +import app.myzel394.alibi.ui.models.AudioRecorderModel import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( - navController: NavController + navController: NavController, + audioRecorder: AudioRecorderModel, ) { - val (_, service) = bindToRecorderService() - val isRecording = service?.isRecording ?: false val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( rememberTopAppBarState() ) @@ -91,7 +90,7 @@ fun SettingsScreen( .value // Show alert - if (isRecording) + if (audioRecorder.isInRecording) Box( modifier = Modifier .padding(16.dp)