Skip to content

Commit 54c6cd3

Browse files
author
izaak
committed
Merge branch 'main' into portrait-mediapipe
2 parents 289799b + 861ce33 commit 54c6cd3

23 files changed

+881
-306
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
1+
<manifest xmlns:tools="http://schemas.android.com/tools"
2+
xmlns:android="http://schemas.android.com/apk/res/android">
23

34
<uses-feature
45
android:name="android.hardware.camera"
@@ -23,7 +24,9 @@
2324

2425
<activity
2526
android:name=".MainActivity"
26-
android:exported="true">
27+
android:screenOrientation="unspecified"
28+
android:exported="true"
29+
tools:ignore="DiscouragedApi">
2730
<intent-filter>
2831
<action android:name="android.intent.action.MAIN"/>
2932
<category android:name="android.intent.category.LAUNCHER"/>

app/src/main/java/co/stonephone/stonecamera/MainActivity.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package co.stonephone.stonecamera
22

33
import android.Manifest
4+
import android.annotation.SuppressLint
5+
import android.content.pm.ActivityInfo
46
import android.content.pm.PackageManager
57
import android.os.Bundle
68
import androidx.activity.ComponentActivity
@@ -9,6 +11,8 @@ import androidx.activity.result.contract.ActivityResultContracts
911
import androidx.camera.lifecycle.ProcessCameraProvider
1012
import androidx.core.content.ContextCompat
1113
import androidx.core.view.WindowCompat
14+
import androidx.core.view.WindowInsetsCompat
15+
import androidx.core.view.WindowInsetsControllerCompat
1216

1317
class MainActivity : ComponentActivity() {
1418

@@ -31,13 +35,28 @@ class MainActivity : ComponentActivity() {
3135
}
3236
}
3337

38+
private fun isChromeOS(): Boolean {
39+
return packageManager.hasSystemFeature("org.chromium.arc.device_management")
40+
}
41+
42+
@SuppressLint("SourceLockedOrientationActivity")
3443
override fun onCreate(savedInstanceState: Bundle?) {
3544
super.onCreate(savedInstanceState)
3645

46+
if (!isChromeOS()) {
47+
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
48+
}
49+
3750
WindowCompat.setDecorFitsSystemWindows(window, false)
3851
window.statusBarColor = android.graphics.Color.TRANSPARENT
3952
window.navigationBarColor = android.graphics.Color.TRANSPARENT
4053

54+
// Hide bottom navigation bar by default
55+
val controller = WindowInsetsControllerCompat(window, window.decorView)
56+
controller.hide(WindowInsetsCompat.Type.systemBars())
57+
controller.systemBarsBehavior =
58+
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
59+
4160
// Check permissions
4261
if (allPermissionsGranted()) {
4362
// All permissions are already granted, proceed
Lines changed: 35 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// StoneCameraApp.kt
2+
@file:kotlin.OptIn(ExperimentalMaterial3Api::class)
3+
24
package co.stonephone.stonecamera
35

46
import android.annotation.SuppressLint
@@ -8,7 +10,6 @@ import androidx.annotation.OptIn
810
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
911
import androidx.camera.lifecycle.ProcessCameraProvider
1012
import androidx.compose.foundation.background
11-
import androidx.compose.foundation.border
1213
import androidx.compose.foundation.clickable
1314
import androidx.compose.foundation.layout.Arrangement
1415
import androidx.compose.foundation.layout.Box
@@ -27,6 +28,7 @@ import androidx.compose.foundation.layout.windowInsetsPadding
2728
import androidx.compose.foundation.shape.CircleShape
2829
import androidx.compose.material.icons.Icons
2930
import androidx.compose.material.icons.filled.FlipCameraAndroid
31+
import androidx.compose.material3.ExperimentalMaterial3Api
3032
import androidx.compose.material3.Icon
3133
import androidx.compose.material3.IconButton
3234
import androidx.compose.material3.MaterialTheme
@@ -48,22 +50,24 @@ import androidx.lifecycle.viewmodel.compose.viewModel
4850
import co.stonephone.stonecamera.plugins.AspectRatioPlugin
4951
import co.stonephone.stonecamera.plugins.FlashPlugin
5052
import co.stonephone.stonecamera.plugins.FocusBasePlugin
53+
import co.stonephone.stonecamera.plugins.PhotoModePlugin
5154
import co.stonephone.stonecamera.plugins.PinchToZoomPlugin
5255
import co.stonephone.stonecamera.plugins.PortraitModePlugin
5356
import co.stonephone.stonecamera.plugins.QRScannerPlugin
5457
import co.stonephone.stonecamera.plugins.SettingLocation
58+
import co.stonephone.stonecamera.plugins.SettingsTrayPlugin
5559
import co.stonephone.stonecamera.plugins.ShutterFlashPlugin
5660
import co.stonephone.stonecamera.plugins.TapToFocusPlugin
5761
import co.stonephone.stonecamera.plugins.VolumeControlsPlugin
62+
import co.stonephone.stonecamera.plugins.VideoModePlugin
5863
import co.stonephone.stonecamera.plugins.ZoomBarPlugin
5964
import co.stonephone.stonecamera.plugins.ZoomBasePlugin
6065
import co.stonephone.stonecamera.ui.RenderPluginSetting
66+
import co.stonephone.stonecamera.ui.ResponsiveOrientation
6167
import co.stonephone.stonecamera.ui.StoneCameraPreview
6268
import co.stonephone.stonecamera.utils.calculateImageCoverageRegion
6369
import co.stonephone.stonecamera.utils.getAllCamerasInfo
6470

65-
val shootModes = arrayOf("Photo", "Video")
66-
6771
// Order here is important, they are loaded and initialised in the order they are listed
6872
// ZoomBar depends on ZoomBase, etc.
6973
val PLUGINS = listOf(
@@ -78,10 +82,15 @@ val PLUGINS = listOf(
7882
AspectRatioPlugin(),
7983
ShutterFlashPlugin(),
8084
VolumeControlsPlugin(),
85+
PhotoModePlugin(),
86+
VideoModePlugin(),
87+
SettingsTrayPlugin()
8188
// DebugPlugin()
8289
)
8390

84-
@OptIn(ExperimentalCamera2Interop::class)
91+
@OptIn(
92+
ExperimentalCamera2Interop::class
93+
)
8594
@SuppressLint("ClickableViewAccessibility")
8695
@Composable
8796
fun StoneCameraApp(
@@ -94,13 +103,15 @@ fun StoneCameraApp(
94103
factory = StoneCameraViewModelFactory(context, lifecycleOwner, PLUGINS)
95104
)
96105

97-
val isRecording = stoneCameraViewModel.isRecording
98106
val selectedMode = stoneCameraViewModel.selectedMode
99107

100108
val plugins by remember { stoneCameraViewModel::plugins }
109+
val modePlugins = plugins.filter { it.modeLabel != null }
101110
val previewView = stoneCameraViewModel.previewView
102111
val imageCapture = stoneCameraViewModel.imageCapture
103112

113+
val activeModePlugin = modePlugins.find { it.modeLabel == selectedMode }
114+
104115
var viewfinderDimensions by remember { mutableStateOf<Rect?>(null) }
105116

106117
LaunchedEffect(cameraProvider, lifecycleOwner) {
@@ -138,9 +149,11 @@ fun StoneCameraApp(
138149
) {
139150
stoneCameraViewModel.pluginSettings.filter { it.renderLocation == SettingLocation.TOP }
140151
.map { setting ->
141-
RenderPluginSetting(
142-
setting, stoneCameraViewModel, modifier = Modifier.padding(4.dp)
143-
)
152+
ResponsiveOrientation {
153+
RenderPluginSetting(
154+
setting, stoneCameraViewModel, modifier = Modifier.padding(4.dp)
155+
)
156+
}
144157
}
145158
}
146159

@@ -164,8 +177,13 @@ fun StoneCameraApp(
164177
.align(Alignment.BottomCenter),
165178
verticalArrangement = Arrangement.SpaceBetween
166179
) {
167-
plugins.map {
168-
it.renderTray(stoneCameraViewModel, it)
180+
181+
Box(
182+
modifier = Modifier.fillMaxWidth(),
183+
) {
184+
plugins.map {
185+
it.renderTray(stoneCameraViewModel, it)
186+
}
169187
}
170188

171189
// Translucent overlay for mode switch & shutter
@@ -186,104 +204,22 @@ fun StoneCameraApp(
186204
.padding(bottom = 8.dp),
187205
horizontalArrangement = Arrangement.SpaceEvenly
188206
) {
189-
shootModes.forEach { mode ->
190-
Text(text = mode.uppercase(),
191-
color = if (mode == selectedMode) Color(0xFFFFCC00) else Color.White,
207+
modePlugins.forEach { modePlugin ->
208+
val modeLabel = modePlugin.modeLabel!!
209+
Text(text = modeLabel.uppercase(),
210+
color = if (modeLabel == selectedMode) Color(0xFFFFCC00) else Color.White,
192211
style = MaterialTheme.typography.bodyLarge,
193212
fontWeight = FontWeight.Bold,
194213
modifier = Modifier.clickable {
195-
stoneCameraViewModel.selectMode(mode)
214+
stoneCameraViewModel.selectMode(modeLabel)
196215
})
197216
}
198217
}
199218

200-
// Bottom row (Flip + Shutter)
201-
Row(
202-
modifier = Modifier.fillMaxWidth(),
203-
horizontalArrangement = Arrangement.SpaceEvenly,
204-
verticalAlignment = Alignment.CenterVertically
205-
) {
206-
// Camera Switcher Button
207-
Box(
208-
modifier = Modifier
209-
.size(48.dp)
210-
.background(Color.Transparent, shape = CircleShape)
211-
.clickable {
212-
stoneCameraViewModel.toggleCameraFacing()
213-
}, contentAlignment = Alignment.Center
214-
) {
215-
216-
}
217-
218-
// Shutter button
219-
Box(
220-
modifier = Modifier
221-
.size(60.dp)
222-
.border(1.dp, Color.White, CircleShape)
223-
.padding(4.dp),
224-
225-
contentAlignment = Alignment.Center
226-
) {
227-
Box(
228-
modifier = if (isRecording) {
229-
Modifier
230-
.background(Color.Red)
231-
.fillMaxSize(0.5f)
232-
} else {
233-
Modifier
234-
.fillMaxSize()
235-
.background(
236-
if (selectedMode == "Video") Color(0xFFFF3B30) else Color.White,
237-
shape = CircleShape
238-
)
239-
240-
241-
}.clickable {
242-
when (selectedMode) {
243-
"Photo" -> {
244-
// Then capture the photo
245-
stoneCameraViewModel.capturePhoto()
246-
}
247-
248-
"Video" -> {
249-
if (!isRecording) {
250-
// Start recording
251-
stoneCameraViewModel.startRecording(
252-
stoneCameraViewModel.videoCapture
253-
) { uri ->
254-
Log.d(
255-
"StoneCameraApp", "Video saved to: $uri"
256-
)
257-
}
258-
} else {
259-
// Stop recording
260-
stoneCameraViewModel.stopRecording()
261-
}
262-
}
263-
}
264-
265-
}, contentAlignment = Alignment.Center
266-
) {}
267-
}
268-
269-
// Camera Switcher Button
270-
IconButton(
271-
onClick = { stoneCameraViewModel.toggleCameraFacing() },
272-
modifier = Modifier
273-
.size(48.dp)
274-
.padding(8.dp)
275-
.background(Color.White.copy(alpha = 0.1f), shape = CircleShape)
276-
) {
277-
Icon(
278-
imageVector = Icons.Filled.FlipCameraAndroid, // Use FlipCameraAndroid if preferred
279-
contentDescription = "Flip Camera",
280-
tint = Color.White, // Customize the color if needed
281-
modifier = Modifier.fillMaxSize()
282-
)
283-
}
284-
}
219+
activeModePlugin?.renderModeControl?.invoke()
285220
}
286221
}
287222
}
223+
288224
}
289225
}

0 commit comments

Comments
 (0)