From e5fa079b260737258ae77244bb790b38a3a3f9c5 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Wed, 25 Oct 2023 21:09:52 +0530 Subject: [PATCH] feat: add floating button to pause recording --- .../recorder/canvas_overlay/CanvasOverlay.kt | 29 +++---- .../recorder/canvas_overlay/ToolbarView.kt | 80 ++++++++++++------- .../recorder/ui/components/AudioVisualizer.kt | 4 +- .../ui/components/RecorderController.kt | 3 +- .../recorder/ui/components/RecorderPreview.kt | 4 +- .../bnyro/recorder/ui/screens/HomeScreen.kt | 4 +- .../recorder/ui/screens/RecorderScreen.kt | 6 +- .../recorder/util/CustomLifecycleOwner.kt | 31 ------- app/src/main/res/values/strings.xml | 2 + 9 files changed, 78 insertions(+), 85 deletions(-) delete mode 100644 app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt diff --git a/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasOverlay.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasOverlay.kt index 66dbe61c..adfc8bb2 100644 --- a/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasOverlay.kt +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasOverlay.kt @@ -7,23 +7,24 @@ import android.os.Build import android.util.Log import android.view.Gravity import android.view.WindowManager +import androidx.activity.ComponentActivity import androidx.annotation.RequiresApi import androidx.compose.ui.platform.ComposeView import androidx.core.view.isInvisible import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelStore -import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.lifecycle.setViewTreeViewModelStoreOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner import com.bnyro.recorder.ui.theme.RecordYouTheme -import com.bnyro.recorder.util.CustomLifecycleOwner @RequiresApi(Build.VERSION_CODES.O) class CanvasOverlay(context: Context) { private var windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager private var canvasView = ComposeView(context).apply { + val activity = context as ComponentActivity + setViewTreeLifecycleOwner(activity) + setViewTreeViewModelStoreOwner(activity) + setViewTreeSavedStateRegistryOwner(activity) setContent { RecordYouTheme() { MainCanvas() @@ -31,6 +32,10 @@ class CanvasOverlay(context: Context) { } } private var toolbarView = ComposeView(context).apply { + val activity = context as ComponentActivity + setViewTreeLifecycleOwner(activity) + setViewTreeViewModelStoreOwner(activity) + setViewTreeSavedStateRegistryOwner(activity) setContent { RecordYouTheme { ToolbarView(hideCanvas = { hide -> @@ -45,20 +50,6 @@ class CanvasOverlay(context: Context) { } init { - val lifecycleOwner = CustomLifecycleOwner() - val viewModelStoreOwner = object : ViewModelStoreOwner { - override val viewModelStore: ViewModelStore = ViewModelStore() - } - lifecycleOwner.performRestore(null) - lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) - canvasView.setViewTreeLifecycleOwner(lifecycleOwner) - canvasView.setViewTreeViewModelStoreOwner(viewModelStoreOwner) - canvasView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) - - toolbarView.setViewTreeLifecycleOwner(lifecycleOwner) - toolbarView.setViewTreeViewModelStoreOwner(viewModelStoreOwner) - toolbarView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) - hideCanvas() } @@ -68,7 +59,7 @@ class CanvasOverlay(context: Context) { size, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSPARENT, + PixelFormat.TRANSPARENT ) } diff --git a/app/src/main/java/com/bnyro/recorder/canvas_overlay/ToolbarView.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/ToolbarView.kt index 7626fd9b..dfde8674 100644 --- a/app/src/main/java/com/bnyro/recorder/canvas_overlay/ToolbarView.kt +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/ToolbarView.kt @@ -1,11 +1,13 @@ package com.bnyro.recorder.canvas_overlay -import android.content.Intent +import android.os.Build import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Draw -import androidx.compose.material.icons.filled.Stop +import androidx.compose.material.icons.rounded.Clear +import androidx.compose.material.icons.rounded.Draw +import androidx.compose.material.icons.rounded.Pause +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.Stop import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -14,56 +16,76 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.recorder.R -import com.bnyro.recorder.services.RecorderService +import com.bnyro.recorder.enums.RecorderState +import com.bnyro.recorder.ui.models.RecorderModel @Composable fun ToolbarView( hideCanvas: (Boolean) -> Unit, - canvasViewModel: CanvasViewModel = viewModel() + canvasViewModel: CanvasViewModel = viewModel(), + recorderModel: RecorderModel = viewModel() ) { - var currentDrawMode by remember { mutableStateOf(DrawMode.Pen) } + var currentDrawMode by remember { mutableStateOf(DrawMode.Eraser) } Card() { Row { IconButton( onClick = { - currentDrawMode = DrawMode.Pen + currentDrawMode = if (currentDrawMode == DrawMode.Eraser) { + hideCanvas(false) + DrawMode.Pen + } else { + DrawMode.Eraser + } canvasViewModel.currentPath.drawMode = currentDrawMode - hideCanvas(false) } ) { - Icon(imageVector = Icons.Default.Draw, contentDescription = "Draw Mode") - } - IconButton( - onClick = { - currentDrawMode = DrawMode.Eraser - canvasViewModel.currentPath.drawMode = currentDrawMode + if (currentDrawMode == DrawMode.Eraser) { + Icon( + imageVector = Icons.Rounded.Draw, + contentDescription = stringResource(R.string.draw_mode) + ) + } else { + Icon( + painter = painterResource(id = R.drawable.ic_eraser_black_24dp), + contentDescription = stringResource(R.string.erase_mode) + ) } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_eraser_black_24dp), - contentDescription = "Erase Mode" - ) } IconButton(onClick = { hideCanvas(true) canvasViewModel.paths.clear() }) { - Icon(Icons.Default.Close, "Show/Hide Canvas") + Icon(Icons.Rounded.Clear, "Show/Hide Canvas") } - val context = LocalContext.current - IconButton(onClick = { - val intent = Intent().apply { - action = RecorderService.RECORDER_INTENT_ACTION - putExtra(RecorderService.ACTION_EXTRA_KEY, RecorderService.STOP_ACTION) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + IconButton(onClick = { + if (recorderModel.recorderState == RecorderState.PAUSED) { + recorderModel.resumeRecording() + } else { + recorderModel.pauseRecording() + } + }) { + if (recorderModel.recorderState == RecorderState.PAUSED) { + Icon( + Icons.Rounded.PlayArrow, + contentDescription = stringResource(id = R.string.resume) + ) + } else { + Icon( + Icons.Rounded.Pause, + contentDescription = stringResource(id = R.string.pause) + ) + } } - context.sendBroadcast(intent) + } + IconButton(onClick = { + recorderModel.stopRecording() }) { - Icon(Icons.Default.Stop, stringResource(id = R.string.stop)) + Icon(Icons.Rounded.Stop, stringResource(id = R.string.stop)) } } } diff --git a/app/src/main/java/com/bnyro/recorder/ui/components/AudioVisualizer.kt b/app/src/main/java/com/bnyro/recorder/ui/components/AudioVisualizer.kt index b4fb530f..896a6c69 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/components/AudioVisualizer.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/components/AudioVisualizer.kt @@ -1,6 +1,7 @@ package com.bnyro.recorder.ui.components import android.text.format.DateUtils +import androidx.activity.ComponentActivity import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -15,6 +16,7 @@ import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer @@ -27,7 +29,7 @@ import com.bnyro.recorder.util.Preferences fun AudioVisualizer( modifier: Modifier = Modifier ) { - val viewModel: RecorderModel = viewModel() + val viewModel: RecorderModel = viewModel(LocalContext.current as ComponentActivity) val showTimestamps = remember { Preferences.prefs.getBoolean( Preferences.showVisualizerTimestamps, diff --git a/app/src/main/java/com/bnyro/recorder/ui/components/RecorderController.kt b/app/src/main/java/com/bnyro/recorder/ui/components/RecorderController.kt index 98b96dde..8db9724c 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/components/RecorderController.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/components/RecorderController.kt @@ -5,6 +5,7 @@ import android.content.Context import android.media.projection.MediaProjectionManager import android.os.Build import android.text.format.DateUtils +import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background @@ -52,7 +53,7 @@ fun RecorderController( recordScreenMode: Boolean, initialRecorder: RecorderType ) { - val recorderModel: RecorderModel = viewModel() + val recorderModel: RecorderModel = viewModel(LocalContext.current as ComponentActivity) val context = LocalContext.current val mProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager diff --git a/app/src/main/java/com/bnyro/recorder/ui/components/RecorderPreview.kt b/app/src/main/java/com/bnyro/recorder/ui/components/RecorderPreview.kt index 663d12bb..bf91b5f4 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/components/RecorderPreview.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/components/RecorderPreview.kt @@ -1,9 +1,11 @@ package com.bnyro.recorder.ui.components +import androidx.activity.ComponentActivity import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.recorder.R import com.bnyro.recorder.ui.common.BlobIconBox @@ -11,7 +13,7 @@ import com.bnyro.recorder.ui.models.RecorderModel @Composable fun RecorderPreview(recordScreenMode: Boolean) { - val recorderModel: RecorderModel = viewModel() + val recorderModel: RecorderModel = viewModel(LocalContext.current as ComponentActivity) if (recordScreenMode) { BlobIconBox( icon = R.drawable.ic_screen_record diff --git a/app/src/main/java/com/bnyro/recorder/ui/screens/HomeScreen.kt b/app/src/main/java/com/bnyro/recorder/ui/screens/HomeScreen.kt index 04feea51..95496ba8 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/screens/HomeScreen.kt @@ -1,6 +1,7 @@ package com.bnyro.recorder.ui.screens import android.view.SoundEffectConstants +import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column @@ -23,6 +24,7 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel @@ -39,7 +41,7 @@ import kotlinx.coroutines.launch fun HomeScreen( initialRecorder: RecorderType, onNavigate: (Destination) -> Unit, - recorderModel: RecorderModel = viewModel() + recorderModel: RecorderModel = viewModel(LocalContext.current as ComponentActivity) ) { val pagerState = rememberPagerState { 2 } val scope = rememberCoroutineScope() diff --git a/app/src/main/java/com/bnyro/recorder/ui/screens/RecorderScreen.kt b/app/src/main/java/com/bnyro/recorder/ui/screens/RecorderScreen.kt index 68a0f925..64f0aae5 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/screens/RecorderScreen.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/screens/RecorderScreen.kt @@ -1,11 +1,13 @@ package com.bnyro.recorder.ui.screens +import androidx.activity.ComponentActivity import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.recorder.enums.RecorderState import com.bnyro.recorder.enums.RecorderType @@ -19,7 +21,7 @@ fun RecorderView( initialRecorder: RecorderType, recordScreenMode: Boolean ) { - val recorderModel: RecorderModel = viewModel() + val recorderModel: RecorderModel = viewModel(LocalContext.current as ComponentActivity) LaunchedEffect(recorderModel.recorderState) { // update the UI when the recorder gets destroyed by the notification @@ -41,4 +43,4 @@ fun RecorderView( } ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt b/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt deleted file mode 100644 index a38037d7..00000000 --- a/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Credits: https://gist.github.com/handstandsam/6ecff2f39da72c0b38c07aa80bbb5a2f -*/ - -package com.bnyro.recorder.util - -import android.os.Bundle -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleRegistry -import androidx.savedstate.SavedStateRegistry -import androidx.savedstate.SavedStateRegistryController -import androidx.savedstate.SavedStateRegistryOwner - -internal class CustomLifecycleOwner : SavedStateRegistryOwner { - private var mLifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) - private var mSavedStateRegistryController: SavedStateRegistryController = - SavedStateRegistryController.create(this) - - fun handleLifecycleEvent(event: Lifecycle.Event) { - mLifecycleRegistry.handleLifecycleEvent(event) - } - - fun performRestore(savedState: Bundle?) { - mSavedStateRegistryController.performRestore(savedState) - } - - override val lifecycle: Lifecycle - get() = mLifecycleRegistry - override val savedStateRegistry: SavedStateRegistry - get() = mSavedStateRegistryController.savedStateRegistry -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b171a67c..31c03a6d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,4 +81,6 @@ Trimming… Trim Successful Can\'t access selected folder! + Draw Mode + Erase Mode \ No newline at end of file