From 9142a5ecbee5183a7aed6ca549f3591f5a8001a2 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Sun, 9 Jul 2023 22:18:25 +0530 Subject: [PATCH 1/6] Create a sample overlay composable --- app/src/main/AndroidManifest.xml | 2 + .../com/bnyro/recorder/ui/MainActivity.kt | 7 +- .../bnyro/recorder/ui/views/CanvasOverlay.kt | 70 +++++++++++++++++++ .../recorder/util/CustomLifecycleOwner.kt | 48 +++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt create mode 100644 app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 17e59206..2d2ad1d8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,8 @@ + + Recorder.SCREEN else -> Recorder.NONE } - + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val canvasOverlay = CanvasOverlay(this) + canvasOverlay.show() + } setContent { RecordYouTheme( when (val mode = themeModel.themeMode) { diff --git a/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt b/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt new file mode 100644 index 00000000..e9ca56db --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt @@ -0,0 +1,70 @@ +package com.bnyro.recorder.ui.views + +import android.content.Context +import android.content.Context.WINDOW_SERVICE +import android.graphics.PixelFormat +import android.os.Build +import android.util.Log +import android.view.ViewGroup +import android.view.WindowManager +import androidx.annotation.RequiresApi +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.ui.platform.ComposeView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.setViewTreeLifecycleOwner +import androidx.savedstate.setViewTreeSavedStateRegistryOwner +import com.bnyro.recorder.util.CustomLifecycleOwner + + +@RequiresApi(Build.VERSION_CODES.O) +class CanvasOverlay(context: Context) { + private var params: WindowManager.LayoutParams = WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT + ) + private var windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager + + private var composeView = ComposeView(context).apply { + setContent { + Button(onClick = { + this@CanvasOverlay.hide() + }) { + Text("Close") + } + } + } + + init { + val lifecycleOwner = CustomLifecycleOwner() + lifecycleOwner.performRestore(null) + lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) + composeView.setViewTreeLifecycleOwner(lifecycleOwner) + composeView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) + } + + fun show() { + try { + if (composeView.windowToken == null) { + if (composeView.parent == null) { + windowManager.addView(composeView, params) + } + } + } catch (e: Exception) { + Log.e("Show Overlay", e.toString()) + } + } + + fun hide() { + try { + windowManager.removeView(composeView) + composeView.invalidate() + (composeView.parent as ViewGroup).removeAllViews() + } catch (e: Exception) { + Log.e("Hide Overlay", e.toString()) + } + } + +} \ 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 new file mode 100644 index 00000000..4baeddfd --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt @@ -0,0 +1,48 @@ +/* +* 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) + + /** + * @return True if the Lifecycle has been initialized. + */ + val isInitialized: Boolean + get() = true + + + fun setCurrentState(state: Lifecycle.State) { + mLifecycleRegistry.currentState = state + } + + fun handleLifecycleEvent(event: Lifecycle.Event) { + mLifecycleRegistry.handleLifecycleEvent(event) + } + + + fun performRestore(savedState: Bundle?) { + mSavedStateRegistryController.performRestore(savedState) + } + + fun performSave(outBundle: Bundle) { + mSavedStateRegistryController.performSave(outBundle) + } + + override val lifecycle: Lifecycle + get() = mLifecycleRegistry + + override val savedStateRegistry: SavedStateRegistry + get() = mSavedStateRegistryController.savedStateRegistry +} \ No newline at end of file From 94182edfe330d415faca05fa02c413b5103f7298 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Sun, 9 Jul 2023 23:35:40 +0530 Subject: [PATCH 2/6] Implement a fully functional (but minimal) canvas overlay --- .../com/bnyro/recorder/ui/MainActivity.kt | 7 +- .../bnyro/recorder/ui/models/RecorderModel.kt | 7 + .../com/bnyro/recorder/ui/views/Canvas.kt | 150 ++++++++++++++++++ .../bnyro/recorder/ui/views/CanvasOverlay.kt | 21 +-- .../bnyro/recorder/ui/views/OverlayView.kt | 28 ++++ 5 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/bnyro/recorder/ui/views/Canvas.kt create mode 100644 app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt diff --git a/app/src/main/java/com/bnyro/recorder/ui/MainActivity.kt b/app/src/main/java/com/bnyro/recorder/ui/MainActivity.kt index b4a71cb2..0ae8b235 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/MainActivity.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/MainActivity.kt @@ -1,6 +1,5 @@ package com.bnyro.recorder.ui -import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -16,7 +15,6 @@ import com.bnyro.recorder.enums.ThemeMode import com.bnyro.recorder.ui.models.ThemeModel import com.bnyro.recorder.ui.screens.RecorderView import com.bnyro.recorder.ui.theme.RecordYouTheme -import com.bnyro.recorder.ui.views.CanvasOverlay class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -29,10 +27,7 @@ class MainActivity : ComponentActivity() { "screen" -> Recorder.SCREEN else -> Recorder.NONE } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val canvasOverlay = CanvasOverlay(this) - canvasOverlay.show() - } + setContent { RecordYouTheme( when (val mode = themeModel.themeMode) { diff --git a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt index f854134d..17d9ef68 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt @@ -24,6 +24,7 @@ import com.bnyro.recorder.services.AudioRecorderService import com.bnyro.recorder.services.LosslessRecorderService import com.bnyro.recorder.services.RecorderService import com.bnyro.recorder.services.ScreenRecorderService +import com.bnyro.recorder.ui.views.CanvasOverlay import com.bnyro.recorder.util.PermissionHelper import com.bnyro.recorder.util.Preferences @@ -34,6 +35,7 @@ class RecorderModel : ViewModel() { var recordedTime by mutableStateOf(null) val recordedAmplitudes = mutableStateListOf() private var activityResult: ActivityResult? = null + var canvasOverlay: CanvasOverlay? = null private val handler = Handler(Looper.getMainLooper()) @@ -59,6 +61,10 @@ class RecorderModel : ViewModel() { activityResult = result val serviceIntent = Intent(context, ScreenRecorderService::class.java) startRecorderService(context, serviceIntent) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvasOverlay = CanvasOverlay(context) + canvasOverlay?.show() + } } @SuppressLint("NewApi") @@ -98,6 +104,7 @@ class RecorderModel : ViewModel() { fun stopRecording() { recorderService?.onDestroy() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) canvasOverlay?.remove() recordedTime = null recordedAmplitudes.clear() } diff --git a/app/src/main/java/com/bnyro/recorder/ui/views/Canvas.kt b/app/src/main/java/com/bnyro/recorder/ui/views/Canvas.kt new file mode 100644 index 00000000..d43ae2fc --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/ui/views/Canvas.kt @@ -0,0 +1,150 @@ +package com.bnyro.recorder.ui.views + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.positionChange + +enum class MotionEvent { + Up, Down, Idle, Move +} + +enum class DrawMode { + Pen, Eraser +} + +@Composable +fun MainCanvas() { + val paths = remember { + mutableStateListOf() + } + val pathsUndone = remember { mutableStateListOf() } + var motionEvent by remember { mutableStateOf(MotionEvent.Idle) } + var currentPath by remember { mutableStateOf(PathProperties()) } + + var currentPosition by remember { mutableStateOf(Offset.Unspecified) } + + val drawModifier = Modifier + .fillMaxSize() + .pointerInput(Unit) { + awaitEachGesture { + val downEvent = awaitFirstDown() + currentPosition = downEvent.position + motionEvent = MotionEvent.Down + if (downEvent.pressed != downEvent.previousPressed) downEvent.consume() + do { + val event = awaitPointerEvent() + if (event.changes.size == 1) { + currentPosition = event.changes[0].position + motionEvent = MotionEvent.Move + if (event.changes[0].positionChange() != Offset.Zero) event.changes[0].consume() + } + } while (event.changes.any { it.pressed }) + motionEvent = MotionEvent.Up + + } + } + Canvas(modifier = drawModifier) { + with(drawContext.canvas.nativeCanvas) { + val checkPoint = saveLayer(null, null) + when (motionEvent) { + MotionEvent.Idle -> Unit + MotionEvent.Down -> { + paths.add(currentPath) + currentPath.path.moveTo( + currentPosition.x, currentPosition.y + ) + } + + MotionEvent.Move -> { + currentPath.path.lineTo( + currentPosition.x, currentPosition.y + ) + drawCircle( + center = currentPosition, + color = Color.Gray, + radius = currentPath.strokeWidth / 2, + style = Stroke( + width = 1f + ) + ) + } + + MotionEvent.Up -> { + currentPath.path.lineTo( + currentPosition.x, currentPosition.y + ) + currentPath = PathProperties( + path = Path(), + strokeWidth = currentPath.strokeWidth, + color = currentPath.color, + drawMode = currentPath.drawMode + ) + pathsUndone.clear() + currentPosition = Offset.Unspecified + motionEvent = MotionEvent.Idle + } + } + paths.forEach { path -> + path.draw(this@Canvas) + } + restoreToCount(checkPoint) + } + + } +} + +class PathProperties( + var path: Path = Path(), + var strokeWidth: Float = 10f, + var color: Color = Color.Red, + var drawMode: DrawMode = DrawMode.Pen +) { + fun draw(scope: DrawScope) { + when (drawMode) { + DrawMode.Pen -> { + + scope.drawPath( + color = color, + path = path, + style = Stroke( + width = strokeWidth, + cap = StrokeCap.Round, + join = StrokeJoin.Round + ) + ) + } + + DrawMode.Eraser -> { + scope.drawPath( + color = Color.Transparent, + path = path, + style = Stroke( + width = strokeWidth, + cap = StrokeCap.Round, + join = StrokeJoin.Round + ), + blendMode = BlendMode.Clear + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt b/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt index e9ca56db..f8886de2 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt @@ -8,8 +8,6 @@ import android.util.Log import android.view.ViewGroup import android.view.WindowManager import androidx.annotation.RequiresApi -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.Lifecycle import androidx.lifecycle.setViewTreeLifecycleOwner @@ -23,17 +21,14 @@ class CanvasOverlay(context: Context) { WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT + PixelFormat.TRANSPARENT ) private var windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager private var composeView = ComposeView(context).apply { setContent { - Button(onClick = { - this@CanvasOverlay.hide() - }) { - Text("Close") - } + OverlayView( + onDismissRequest = { this@CanvasOverlay.remove() }) } } @@ -58,12 +53,20 @@ class CanvasOverlay(context: Context) { } fun hide() { + try { + windowManager.removeView(composeView) + } catch (e: Exception) { + Log.e("Hide Overlay", e.toString()) + } + } + + fun remove() { try { windowManager.removeView(composeView) composeView.invalidate() (composeView.parent as ViewGroup).removeAllViews() } catch (e: Exception) { - Log.e("Hide Overlay", e.toString()) + Log.e("Remove Overlay", e.toString()) } } diff --git a/app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt b/app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt new file mode 100644 index 00000000..38f1ab23 --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt @@ -0,0 +1,28 @@ +package com.bnyro.recorder.ui.views + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun OverlayView(onDismissRequest: () -> Unit) { + Box(Modifier.fillMaxSize()) { + MainCanvas() + Card(Modifier.align(Alignment.TopEnd)) { + Row { + IconButton(onClick = { onDismissRequest() }) { + Icon(Icons.Default.Close, "Close Overlay") + } + } + } + } + +} \ No newline at end of file From e6afc4bc0da9af50c8ac165455bb74e596eb9c7b Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Mon, 10 Jul 2023 16:59:31 +0530 Subject: [PATCH 3/6] Add eraser mode --- .../{ui/views => canvas_overlay}/Canvas.kt | 26 ++++----- .../views => canvas_overlay}/CanvasOverlay.kt | 13 +++-- .../canvas_overlay/CanvasViewModel.kt | 10 ++++ .../recorder/canvas_overlay/OverlayView.kt | 54 +++++++++++++++++++ .../bnyro/recorder/ui/models/RecorderModel.kt | 2 +- .../bnyro/recorder/ui/views/OverlayView.kt | 28 ---------- .../util/CustomViewModelStoreOwner.kt | 9 ++++ .../res/drawable/ic_eraser_black_24dp.xml | 9 ++++ 8 files changed, 106 insertions(+), 45 deletions(-) rename app/src/main/java/com/bnyro/recorder/{ui/views => canvas_overlay}/Canvas.kt (84%) rename app/src/main/java/com/bnyro/recorder/{ui/views => canvas_overlay}/CanvasOverlay.kt (81%) create mode 100644 app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt create mode 100644 app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt delete mode 100644 app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt create mode 100644 app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt create mode 100644 app/src/main/res/drawable/ic_eraser_black_24dp.xml diff --git a/app/src/main/java/com/bnyro/recorder/ui/views/Canvas.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/Canvas.kt similarity index 84% rename from app/src/main/java/com/bnyro/recorder/ui/views/Canvas.kt rename to app/src/main/java/com/bnyro/recorder/canvas_overlay/Canvas.kt index d43ae2fc..e9774fed 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/views/Canvas.kt +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/Canvas.kt @@ -1,4 +1,4 @@ -package com.bnyro.recorder.ui.views +package com.bnyro.recorder.canvas_overlay import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.awaitEachGesture @@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange +import androidx.lifecycle.viewmodel.compose.viewModel enum class MotionEvent { Up, Down, Idle, Move @@ -32,13 +33,12 @@ enum class DrawMode { } @Composable -fun MainCanvas() { +fun MainCanvas(canvasViewModel: CanvasViewModel = viewModel()) { val paths = remember { mutableStateListOf() } val pathsUndone = remember { mutableStateListOf() } var motionEvent by remember { mutableStateOf(MotionEvent.Idle) } - var currentPath by remember { mutableStateOf(PathProperties()) } var currentPosition by remember { mutableStateOf(Offset.Unspecified) } @@ -68,20 +68,20 @@ fun MainCanvas() { when (motionEvent) { MotionEvent.Idle -> Unit MotionEvent.Down -> { - paths.add(currentPath) - currentPath.path.moveTo( + paths.add(canvasViewModel.currentPath) + canvasViewModel.currentPath.path.moveTo( currentPosition.x, currentPosition.y ) } MotionEvent.Move -> { - currentPath.path.lineTo( + canvasViewModel.currentPath.path.lineTo( currentPosition.x, currentPosition.y ) drawCircle( center = currentPosition, color = Color.Gray, - radius = currentPath.strokeWidth / 2, + radius = canvasViewModel.currentPath.strokeWidth / 2, style = Stroke( width = 1f ) @@ -89,14 +89,14 @@ fun MainCanvas() { } MotionEvent.Up -> { - currentPath.path.lineTo( + canvasViewModel.currentPath.path.lineTo( currentPosition.x, currentPosition.y ) - currentPath = PathProperties( + canvasViewModel.currentPath = PathProperties( path = Path(), - strokeWidth = currentPath.strokeWidth, - color = currentPath.color, - drawMode = currentPath.drawMode + strokeWidth = canvasViewModel.currentPath.strokeWidth, + color = canvasViewModel.currentPath.color, + drawMode = canvasViewModel.currentPath.drawMode ) pathsUndone.clear() currentPosition = Offset.Unspecified @@ -138,7 +138,7 @@ class PathProperties( color = Color.Transparent, path = path, style = Stroke( - width = strokeWidth, + width = 50f, cap = StrokeCap.Round, join = StrokeJoin.Round ), diff --git a/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasOverlay.kt similarity index 81% rename from app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt rename to app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasOverlay.kt index f8886de2..93f2e330 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/views/CanvasOverlay.kt +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasOverlay.kt @@ -1,4 +1,4 @@ -package com.bnyro.recorder.ui.views +package com.bnyro.recorder.canvas_overlay import android.content.Context import android.content.Context.WINDOW_SERVICE @@ -11,8 +11,11 @@ import androidx.annotation.RequiresApi import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.Lifecycle 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 +import com.bnyro.recorder.util.CustomViewModelStoreOwner @RequiresApi(Build.VERSION_CODES.O) @@ -27,16 +30,20 @@ class CanvasOverlay(context: Context) { private var composeView = ComposeView(context).apply { setContent { - OverlayView( - onDismissRequest = { this@CanvasOverlay.remove() }) + RecordYouTheme() { + OverlayView( + onDismissRequest = { this@CanvasOverlay.remove() }) + } } } init { val lifecycleOwner = CustomLifecycleOwner() + val viewModelStoreOwner = CustomViewModelStoreOwner() lifecycleOwner.performRestore(null) lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) composeView.setViewTreeLifecycleOwner(lifecycleOwner) + composeView.setViewTreeViewModelStoreOwner(viewModelStoreOwner) composeView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) } diff --git a/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt new file mode 100644 index 00000000..d3e98b72 --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt @@ -0,0 +1,10 @@ +package com.bnyro.recorder.canvas_overlay + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel + +class CanvasViewModel: ViewModel() { + var currentPath by mutableStateOf(PathProperties()) +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt new file mode 100644 index 00000000..a5e0c797 --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt @@ -0,0 +1,54 @@ +package com.bnyro.recorder.canvas_overlay + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Draw +import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.lifecycle.viewmodel.compose.viewModel +import com.bnyro.recorder.R + +@Composable +fun OverlayView(onDismissRequest: () -> Unit, canvasViewModel: CanvasViewModel = viewModel()) { + Box(Modifier.fillMaxSize()) { + MainCanvas() + var currentDrawMode by remember { mutableStateOf(canvasViewModel.currentPath.drawMode) } + Card(Modifier.align(Alignment.TopEnd)) { + Row { + IconButton( + onClick = { + currentDrawMode = DrawMode.Pen + canvasViewModel.currentPath.drawMode = currentDrawMode + }) { + Icon(imageVector = Icons.Default.Draw, contentDescription = "Draw Mode") + } + IconButton( + onClick = { + currentDrawMode = DrawMode.Eraser + canvasViewModel.currentPath.drawMode = currentDrawMode + }) { + Icon( + painter = painterResource(id = R.drawable.ic_eraser_black_24dp), + contentDescription = "Erase Mode" + ) + } + IconButton(onClick = { onDismissRequest() }) { + Icon(Icons.Default.Close, "Close Overlay") + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt index 17d9ef68..fab32059 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt @@ -24,7 +24,7 @@ import com.bnyro.recorder.services.AudioRecorderService import com.bnyro.recorder.services.LosslessRecorderService import com.bnyro.recorder.services.RecorderService import com.bnyro.recorder.services.ScreenRecorderService -import com.bnyro.recorder.ui.views.CanvasOverlay +import com.bnyro.recorder.canvas_overlay.CanvasOverlay import com.bnyro.recorder.util.PermissionHelper import com.bnyro.recorder.util.Preferences diff --git a/app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt b/app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt deleted file mode 100644 index 38f1ab23..00000000 --- a/app/src/main/java/com/bnyro/recorder/ui/views/OverlayView.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.bnyro.recorder.ui.views - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.Card -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier - -@Composable -fun OverlayView(onDismissRequest: () -> Unit) { - Box(Modifier.fillMaxSize()) { - MainCanvas() - Card(Modifier.align(Alignment.TopEnd)) { - Row { - IconButton(onClick = { onDismissRequest() }) { - Icon(Icons.Default.Close, "Close Overlay") - } - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt b/app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt new file mode 100644 index 00000000..18a2d7ec --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt @@ -0,0 +1,9 @@ +package com.bnyro.recorder.util + +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner + +class CustomViewModelStoreOwner:ViewModelStoreOwner { + override val viewModelStore: ViewModelStore = ViewModelStore() + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_eraser_black_24dp.xml b/app/src/main/res/drawable/ic_eraser_black_24dp.xml new file mode 100644 index 00000000..1283eaab --- /dev/null +++ b/app/src/main/res/drawable/ic_eraser_black_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file From f989d6ffaec3e6917a8882975d48ede8bb6ed8ad Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Tue, 11 Jul 2023 16:25:47 +0530 Subject: [PATCH 4/6] Add option to show hide canvas Fix minor formatting issues --- .../bnyro/recorder/canvas_overlay/Canvas.kt | 10 +-- .../recorder/canvas_overlay/CanvasOverlay.kt | 76 +++++++++++++------ .../canvas_overlay/CanvasViewModel.kt | 4 +- .../recorder/canvas_overlay/OverlayView.kt | 54 ------------- .../recorder/canvas_overlay/ToolbarView.kt | 53 +++++++++++++ .../bnyro/recorder/ui/models/RecorderModel.kt | 2 +- .../recorder/util/CustomLifecycleOwner.kt | 17 ----- .../util/CustomViewModelStoreOwner.kt | 9 --- 8 files changed, 110 insertions(+), 115 deletions(-) delete mode 100644 app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt create mode 100644 app/src/main/java/com/bnyro/recorder/canvas_overlay/ToolbarView.kt delete mode 100644 app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt diff --git a/app/src/main/java/com/bnyro/recorder/canvas_overlay/Canvas.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/Canvas.kt index e9774fed..0e73514c 100644 --- a/app/src/main/java/com/bnyro/recorder/canvas_overlay/Canvas.kt +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/Canvas.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -34,10 +33,6 @@ enum class DrawMode { @Composable fun MainCanvas(canvasViewModel: CanvasViewModel = viewModel()) { - val paths = remember { - mutableStateListOf() - } - val pathsUndone = remember { mutableStateListOf() } var motionEvent by remember { mutableStateOf(MotionEvent.Idle) } var currentPosition by remember { mutableStateOf(Offset.Unspecified) } @@ -68,7 +63,7 @@ fun MainCanvas(canvasViewModel: CanvasViewModel = viewModel()) { when (motionEvent) { MotionEvent.Idle -> Unit MotionEvent.Down -> { - paths.add(canvasViewModel.currentPath) + canvasViewModel.paths.add(canvasViewModel.currentPath) canvasViewModel.currentPath.path.moveTo( currentPosition.x, currentPosition.y ) @@ -98,12 +93,11 @@ fun MainCanvas(canvasViewModel: CanvasViewModel = viewModel()) { color = canvasViewModel.currentPath.color, drawMode = canvasViewModel.currentPath.drawMode ) - pathsUndone.clear() currentPosition = Offset.Unspecified motionEvent = MotionEvent.Idle } } - paths.forEach { path -> + canvasViewModel.paths.forEach { path -> path.draw(this@Canvas) } restoreToCount(checkPoint) 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 93f2e330..fe2b5f47 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 @@ -5,17 +5,20 @@ import android.content.Context.WINDOW_SERVICE import android.graphics.PixelFormat import android.os.Build import android.util.Log -import android.view.ViewGroup +import android.view.Gravity import android.view.WindowManager 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 -import com.bnyro.recorder.util.CustomViewModelStoreOwner @RequiresApi(Build.VERSION_CODES.O) @@ -27,54 +30,77 @@ class CanvasOverlay(context: Context) { PixelFormat.TRANSPARENT ) private var windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager - - private var composeView = ComposeView(context).apply { + private var canvasView = ComposeView(context).apply { setContent { RecordYouTheme() { - OverlayView( - onDismissRequest = { this@CanvasOverlay.remove() }) + MainCanvas() + } + } + } + private var toolbarView = ComposeView(context).apply { + setContent { + RecordYouTheme { + ToolbarView(hideCanvas = { hide -> + if (hide) { + hideCanvas() + } else { + showCanvas() + } + }) } } } init { val lifecycleOwner = CustomLifecycleOwner() - val viewModelStoreOwner = CustomViewModelStoreOwner() + val viewModelStoreOwner = object : ViewModelStoreOwner { + override val viewModelStore: ViewModelStore = ViewModelStore() + } lifecycleOwner.performRestore(null) lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) - composeView.setViewTreeLifecycleOwner(lifecycleOwner) - composeView.setViewTreeViewModelStoreOwner(viewModelStoreOwner) - composeView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) + canvasView.setViewTreeLifecycleOwner(lifecycleOwner) + canvasView.setViewTreeViewModelStoreOwner(viewModelStoreOwner) + canvasView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) + + toolbarView.setViewTreeLifecycleOwner(lifecycleOwner) + toolbarView.setViewTreeViewModelStoreOwner(viewModelStoreOwner) + toolbarView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) + + hideCanvas() } - fun show() { + fun showAll() { try { - if (composeView.windowToken == null) { - if (composeView.parent == null) { - windowManager.addView(composeView, params) - } + if (canvasView.windowToken == null && canvasView.parent == null) { + windowManager.addView(canvasView, params) + + } + if (toolbarView.windowToken == null && toolbarView.parent == null) { + val toolbarParams = params + toolbarParams.gravity = Gravity.TOP or Gravity.END + windowManager.addView(toolbarView, toolbarParams) } } catch (e: Exception) { Log.e("Show Overlay", e.toString()) } } - fun hide() { - try { - windowManager.removeView(composeView) - } catch (e: Exception) { - Log.e("Hide Overlay", e.toString()) - } + fun showCanvas() { + canvasView.isVisible = true + } + + fun hideCanvas() { + canvasView.isInvisible = true } fun remove() { try { - windowManager.removeView(composeView) - composeView.invalidate() - (composeView.parent as ViewGroup).removeAllViews() + windowManager.removeView(canvasView) + canvasView.invalidate() + windowManager.removeView(toolbarView) + toolbarView.invalidate() } catch (e: Exception) { Log.e("Remove Overlay", e.toString()) } } - } \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt index d3e98b72..025d33eb 100644 --- a/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt @@ -1,10 +1,12 @@ package com.bnyro.recorder.canvas_overlay import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -class CanvasViewModel: ViewModel() { +class CanvasViewModel : ViewModel() { var currentPath by mutableStateOf(PathProperties()) + val paths = mutableStateListOf() } \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt b/app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt deleted file mode 100644 index a5e0c797..00000000 --- a/app/src/main/java/com/bnyro/recorder/canvas_overlay/OverlayView.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.bnyro.recorder.canvas_overlay - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Draw -import androidx.compose.material3.Card -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.lifecycle.viewmodel.compose.viewModel -import com.bnyro.recorder.R - -@Composable -fun OverlayView(onDismissRequest: () -> Unit, canvasViewModel: CanvasViewModel = viewModel()) { - Box(Modifier.fillMaxSize()) { - MainCanvas() - var currentDrawMode by remember { mutableStateOf(canvasViewModel.currentPath.drawMode) } - Card(Modifier.align(Alignment.TopEnd)) { - Row { - IconButton( - onClick = { - currentDrawMode = DrawMode.Pen - canvasViewModel.currentPath.drawMode = currentDrawMode - }) { - Icon(imageVector = Icons.Default.Draw, contentDescription = "Draw Mode") - } - IconButton( - onClick = { - currentDrawMode = DrawMode.Eraser - canvasViewModel.currentPath.drawMode = currentDrawMode - }) { - Icon( - painter = painterResource(id = R.drawable.ic_eraser_black_24dp), - contentDescription = "Erase Mode" - ) - } - IconButton(onClick = { onDismissRequest() }) { - Icon(Icons.Default.Close, "Close Overlay") - } - } - } - } - -} \ No newline at end of file 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 new file mode 100644 index 00000000..64f03932 --- /dev/null +++ b/app/src/main/java/com/bnyro/recorder/canvas_overlay/ToolbarView.kt @@ -0,0 +1,53 @@ +package com.bnyro.recorder.canvas_overlay + +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.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.painterResource +import androidx.lifecycle.viewmodel.compose.viewModel +import com.bnyro.recorder.R + +@Composable +fun ToolbarView( + hideCanvas: (Boolean) -> Unit, + canvasViewModel: CanvasViewModel = viewModel() +) { + var currentDrawMode by remember { mutableStateOf(DrawMode.Pen) } + Card() { + Row { + IconButton( + onClick = { + currentDrawMode = DrawMode.Pen + canvasViewModel.currentPath.drawMode = currentDrawMode + hideCanvas(false) + }) { + Icon(imageVector = Icons.Default.Draw, contentDescription = "Draw Mode") + } + IconButton( + onClick = { + currentDrawMode = DrawMode.Eraser + canvasViewModel.currentPath.drawMode = currentDrawMode + }) { + 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") + } + } + } +} diff --git a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt index fab32059..323a2931 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt @@ -63,7 +63,7 @@ class RecorderModel : ViewModel() { startRecorderService(context, serviceIntent) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { canvasOverlay = CanvasOverlay(context) - canvasOverlay?.show() + canvasOverlay?.showAll() } } diff --git a/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt b/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt index 4baeddfd..3c691413 100644 --- a/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt +++ b/app/src/main/java/com/bnyro/recorder/util/CustomLifecycleOwner.kt @@ -16,33 +16,16 @@ internal class CustomLifecycleOwner : SavedStateRegistryOwner { private var mSavedStateRegistryController: SavedStateRegistryController = SavedStateRegistryController.create(this) - /** - * @return True if the Lifecycle has been initialized. - */ - val isInitialized: Boolean - get() = true - - - fun setCurrentState(state: Lifecycle.State) { - mLifecycleRegistry.currentState = state - } - fun handleLifecycleEvent(event: Lifecycle.Event) { mLifecycleRegistry.handleLifecycleEvent(event) } - fun performRestore(savedState: Bundle?) { mSavedStateRegistryController.performRestore(savedState) } - fun performSave(outBundle: Bundle) { - mSavedStateRegistryController.performSave(outBundle) - } - override val lifecycle: Lifecycle get() = mLifecycleRegistry - override val savedStateRegistry: SavedStateRegistry get() = mSavedStateRegistryController.savedStateRegistry } \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt b/app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt deleted file mode 100644 index 18a2d7ec..00000000 --- a/app/src/main/java/com/bnyro/recorder/util/CustomViewModelStoreOwner.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bnyro.recorder.util - -import androidx.lifecycle.ViewModelStore -import androidx.lifecycle.ViewModelStoreOwner - -class CustomViewModelStoreOwner:ViewModelStoreOwner { - override val viewModelStore: ViewModelStore = ViewModelStore() - -} \ No newline at end of file From 4d79f0da0850afd36ed7dbbab35c66ed9f3ca545 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Tue, 11 Jul 2023 19:40:53 +0530 Subject: [PATCH 5/6] Add overalay preference item Add annotation tool settings to bottom sheet Switch back to using Preferences object for managing preferences --- .../bnyro/recorder/ui/components/SettingsBottomSheet.kt | 8 ++++++++ .../java/com/bnyro/recorder/ui/models/RecorderModel.kt | 6 ++++-- app/src/main/java/com/bnyro/recorder/util/Preferences.kt | 3 ++- app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/bnyro/recorder/ui/components/SettingsBottomSheet.kt b/app/src/main/java/com/bnyro/recorder/ui/components/SettingsBottomSheet.kt index 3edef16b..0f8b4218 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/components/SettingsBottomSheet.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/components/SettingsBottomSheet.kt @@ -228,6 +228,14 @@ fun SettingsBottomSheet( ) } Spacer(modifier = Modifier.height(10.dp)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CheckboxPref( + prefKey = Preferences.showOverlayAnnotationToolKey, + title = stringResource(R.string.screen_recorder_annotation), + summary = stringResource(R.string.screen_recorder_annotation_desc) + ) + } + Spacer(modifier = Modifier.height(10.dp)) NamingPatternPref() } diff --git a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt index 323a2931..b3318438 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt @@ -18,13 +18,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModel +import com.bnyro.recorder.canvas_overlay.CanvasOverlay import com.bnyro.recorder.enums.AudioSource import com.bnyro.recorder.enums.RecorderState import com.bnyro.recorder.services.AudioRecorderService import com.bnyro.recorder.services.LosslessRecorderService import com.bnyro.recorder.services.RecorderService import com.bnyro.recorder.services.ScreenRecorderService -import com.bnyro.recorder.canvas_overlay.CanvasOverlay import com.bnyro.recorder.util.PermissionHelper import com.bnyro.recorder.util.Preferences @@ -61,7 +61,9 @@ class RecorderModel : ViewModel() { activityResult = result val serviceIntent = Intent(context, ScreenRecorderService::class.java) startRecorderService(context, serviceIntent) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val showOverlayAnnotation = + Preferences.prefs.getBoolean(Preferences.showOverlayAnnotationToolKey, false) + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) && showOverlayAnnotation) { canvasOverlay = CanvasOverlay(context) canvasOverlay?.showAll() } diff --git a/app/src/main/java/com/bnyro/recorder/util/Preferences.kt b/app/src/main/java/com/bnyro/recorder/util/Preferences.kt index bebfe9b5..5c1edbcb 100644 --- a/app/src/main/java/com/bnyro/recorder/util/Preferences.kt +++ b/app/src/main/java/com/bnyro/recorder/util/Preferences.kt @@ -19,6 +19,7 @@ object Preferences { const val themeModeKey = "themeMode" const val losslessRecorderKey = "losslessRecorder" const val namingPatternKey = "namingPattern" + const val showOverlayAnnotationToolKey = "annotationTool" fun init(context: Context) { prefs = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE) @@ -29,4 +30,4 @@ object Preferences { } fun getString(key: String, defValue: String) = prefs.getString(key, defValue) ?: defValue -} +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c19a040b..5675c40d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -64,4 +64,6 @@ System Dark Light + Screen Record Annotation + Show annotation tool during screen recording \ No newline at end of file From fb511c4b53d3c9258d17b1a36b9e20a2432de16c Mon Sep 17 00:00:00 2001 From: Bnyro Date: Tue, 11 Jul 2023 16:55:42 +0200 Subject: [PATCH 6/6] Prompt users for overlay permission on screen recording --- .../bnyro/recorder/ui/models/RecorderModel.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt index b3318438..1659fd4e 100644 --- a/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt +++ b/app/src/main/java/com/bnyro/recorder/ui/models/RecorderModel.kt @@ -6,16 +6,19 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.net.Uri import android.os.Build import android.os.Handler import android.os.IBinder import android.os.Looper +import android.provider.Settings import androidx.activity.result.ActivityResult import androidx.annotation.RequiresApi import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.app.ActivityCompat.startActivityForResult import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModel import com.bnyro.recorder.canvas_overlay.CanvasOverlay @@ -28,14 +31,16 @@ import com.bnyro.recorder.services.ScreenRecorderService import com.bnyro.recorder.util.PermissionHelper import com.bnyro.recorder.util.Preferences + class RecorderModel : ViewModel() { private val audioPermission = arrayOf(Manifest.permission.RECORD_AUDIO) + private val supportsOverlay = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O var recorderState by mutableStateOf(RecorderState.IDLE) var recordedTime by mutableStateOf(null) val recordedAmplitudes = mutableStateListOf() private var activityResult: ActivityResult? = null - var canvasOverlay: CanvasOverlay? = null + private var canvasOverlay: CanvasOverlay? = null private val handler = Handler(Looper.getMainLooper()) @@ -63,7 +68,7 @@ class RecorderModel : ViewModel() { startRecorderService(context, serviceIntent) val showOverlayAnnotation = Preferences.prefs.getBoolean(Preferences.showOverlayAnnotationToolKey, false) - if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) && showOverlayAnnotation) { + if (supportsOverlay && showOverlayAnnotation) { canvasOverlay = CanvasOverlay(context) canvasOverlay?.showAll() } @@ -148,7 +153,18 @@ class RecorderModel : ViewModel() { handler.postDelayed(this::updateTime, 1000) } + @SuppressLint("NewApi") fun hasScreenRecordingPermissions(context: Context): Boolean { + val overlayEnabled = Preferences.prefs.getBoolean(Preferences.showOverlayAnnotationToolKey, true) + if (supportsOverlay && overlayEnabled && !Settings.canDrawOverlays(context)) { + val intent = Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + context.packageName) + ) + context.startActivity(intent) + return false + } + val requiredPermissions = arrayListOf() val recordAudio =