This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: fix sample app UI

+167 -65
+142 -65
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
··· 2 2 3 3 import androidx.compose.animation.AnimatedContent 4 4 import androidx.compose.foundation.Image 5 + import androidx.compose.foundation.layout.Arrangement 5 6 import androidx.compose.foundation.layout.Box 6 7 import androidx.compose.foundation.layout.Column 8 + import androidx.compose.foundation.layout.Row 9 + import androidx.compose.foundation.layout.Spacer 7 10 import androidx.compose.foundation.layout.WindowInsets 8 11 import androidx.compose.foundation.layout.fillMaxSize 9 12 import androidx.compose.foundation.layout.fillMaxWidth 13 + import androidx.compose.foundation.layout.height 10 14 import androidx.compose.foundation.layout.imePadding 11 15 import androidx.compose.foundation.layout.padding 12 16 import androidx.compose.foundation.layout.safeDrawing 17 + import androidx.compose.foundation.layout.width 13 18 import androidx.compose.foundation.layout.windowInsetsPadding 14 19 import androidx.compose.material3.Button 15 20 import androidx.compose.material3.CircularProgressIndicator 21 + import androidx.compose.material3.DropdownMenu 22 + import androidx.compose.material3.DropdownMenuItem 23 + import androidx.compose.material3.OutlinedButton 16 24 import androidx.compose.material3.Tab 17 25 import androidx.compose.material3.TabRow 18 26 import androidx.compose.material3.Text 27 + import androidx.compose.material3.TextButton 28 + import androidx.compose.material3.Icon 29 + import androidx.compose.material3.IconButton 30 + import androidx.compose.material3.Switch 31 + import androidx.compose.material3.HorizontalDivider 19 32 import androidx.compose.runtime.Composable 20 33 import androidx.compose.runtime.DisposableEffect 21 34 import androidx.compose.runtime.LaunchedEffect 22 35 import androidx.compose.runtime.collectAsState 23 36 import androidx.compose.runtime.getValue 37 + import androidx.compose.runtime.key 24 38 import androidx.compose.runtime.mutableStateOf 25 39 import androidx.compose.runtime.remember 26 40 import androidx.compose.runtime.rememberCoroutineScope ··· 32 46 import androidx.compose.ui.graphics.ImageBitmap 33 47 import androidx.compose.ui.graphics.drawscope.Stroke 34 48 import androidx.compose.ui.layout.ContentScale 49 + import androidx.compose.ui.text.font.FontWeight 35 50 import androidx.compose.ui.unit.dp 36 51 import androidx.compose.ui.unit.sp 37 52 import chaintech.videoplayer.host.MediaPlayerHost ··· 304 319 } 305 320 } 306 321 322 + private enum class ZoomChoice(val label: String) { 323 + ZOOM_1X("1.0x"), 324 + ZOOM_0_5X("0.5x"), 325 + } 326 + 307 327 @OptIn(ExperimentalTime::class) 308 328 @Composable 309 329 fun CameraSample() { ··· 320 340 var frontCamera by remember { mutableStateOf(false) } 321 341 var ultrawide by remember { mutableStateOf(false) } 322 342 var previewFillMode by remember { mutableStateOf(PreviewFillMode.FIT) } 343 + var menuExpanded by remember { mutableStateOf(false) } 344 + 323 345 val controller = remember { CameraViewControllerImpl() } 324 346 PermissionProvider().apply { 325 347 if (!hasCameraPermission()) RequestCameraPermission(onGranted = { 326 348 permissionGranted = true 327 349 }, onDenied = { permissionGranted = false }) else permissionGranted = true 328 350 } 351 + 329 352 if (permissionGranted) { 330 - Box(modifier = Modifier.fillMaxSize()) { 331 - Column(modifier = Modifier.fillMaxSize()) { 332 - androidx.compose.animation.AnimatedVisibility(path.isNotBlank()) { 333 - val playerHost = remember(path) { 334 - MediaPlayerHost( 335 - mediaUrl = path, 336 - isLooping = true, 337 - isPaused = false, 338 - isMuted = false, 339 - initialVideoFitMode = ScreenResize.FIT, 353 + DetectOrientation{ orientation -> 354 + Box(modifier = Modifier.fillMaxSize()) { 355 + Column(modifier = Modifier.fillMaxSize()) { 356 + androidx.compose.animation.AnimatedVisibility(path.isNotBlank()) { 357 + val playerHost = remember(path) { 358 + MediaPlayerHost( 359 + mediaUrl = path, 360 + isLooping = true, 361 + isPaused = false, 362 + isMuted = false, 363 + initialVideoFitMode = ScreenResize.FIT, 364 + ) 365 + } 366 + VideoPlayerComposable( 367 + modifier = Modifier.weight(1f), 368 + playerHost = playerHost, 369 + playerConfig = VideoPlayerConfig( 370 + isSeekBarVisible = true, 371 + isDurationVisible = true, 372 + isFastForwardBackwardEnabled = true, 373 + isMuteControlEnabled = true, 374 + isSpeedControlEnabled = true, 375 + isScreenLockEnabled = false, 376 + isScreenResizeEnabled = true, 377 + isFullScreenEnabled = true, 378 + isPauseResumeEnabled = true, 379 + ) 340 380 ) 341 381 } 342 - VideoPlayerComposable( 343 - modifier = Modifier.weight(1f), 344 - playerHost = playerHost, 345 - playerConfig = VideoPlayerConfig( 346 - isSeekBarVisible = true, 347 - isDurationVisible = true, 348 - isFastForwardBackwardEnabled = true, 349 - isMuteControlEnabled = true, 350 - isSpeedControlEnabled = true, 351 - isScreenLockEnabled = false, 352 - isScreenResizeEnabled = true, 353 - isFullScreenEnabled = true, 354 - isPauseResumeEnabled = true, 382 + key(orientation){ 383 + CameraView( 384 + skeletonRepository = skeletonRepository, 385 + customObjectRepository = customObjectRespository, 386 + detectMode = DetectMode.BOTH, 387 + drawSkeleton = true, 388 + drawObjects = { obj -> 389 + obj.flatMap { 390 + listOf( 391 + DrawableObject( 392 + obj = it, 393 + shape = DrawableShape.LABEL, 394 + colour = Color.Yellow, 395 + style = Stroke(it.boundingBox.width * 0.1f) 396 + ), DrawableObject( 397 + obj = it, 398 + shape = DrawableShape.RECTANGLE, 399 + colour = Color.Green, 400 + style = Stroke(it.boundingBox.width * 0.1f) 401 + ) 402 + ) 403 + } 404 + }, 405 + objectModel = generalModel, 406 + modifier = Modifier.weight(1f), 407 + focusArea = Rect(0f,0f,1f,1f), 408 + frontCamera = frontCamera, 409 + useUltraWide = ultrawide, 410 + previewFillMode = previewFillMode, 411 + recordingId = recordingId, 412 + controller = controller, 413 + onVideoSaved = { id, url -> path = url }, 355 414 ) 356 - ) 415 + } 357 416 } 358 - CameraView( 359 - skeletonRepository = skeletonRepository, 360 - customObjectRepository = customObjectRespository, 361 - detectMode = DetectMode.BOTH, 362 - drawSkeleton = true, 363 - drawObjects = { obj -> 364 - obj.flatMap { 365 - listOf( 366 - DrawableObject( 367 - obj = it, 368 - shape = DrawableShape.LABEL, 369 - colour = Color.Yellow, 370 - style = Stroke(it.boundingBox.width * 0.1f) 371 - ), DrawableObject( 372 - obj = it, 373 - shape = DrawableShape.RECTANGLE, 374 - colour = Color.Green, 375 - style = Stroke(it.boundingBox.width * 0.1f) 376 - ) 377 - ) 378 - } 379 - }, 380 - objectModel = generalModel, 381 - modifier = Modifier.weight(1f), 382 - focusArea = Rect(0f,0f,1f,1f), 383 - frontCamera = frontCamera, 384 - useUltraWide = ultrawide, 385 - previewFillMode = previewFillMode, 386 - recordingId = recordingId, 387 - controller = controller, 388 - onVideoSaved = { id, url -> path = url }, 389 - ) 390 - } 391 - Button( 392 - onClick = { 393 - //recordingId = "${Clock.System.now().epochSeconds}" 394 - //ultrawide = !ultrawide 395 - previewFillMode = if(previewFillMode==PreviewFillMode.FIT) PreviewFillMode.FILL else PreviewFillMode.FIT 396 - }, modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart) 397 - ) { 398 - Text("Start Recording") 417 + Box( 418 + modifier = Modifier 419 + .imePadding() 420 + .padding(12.dp) 421 + .align(Alignment.TopEnd) 422 + ) { 423 + IconButton(onClick = { menuExpanded = true }) { 424 + Text("⋮", color = Color.White, fontSize = 22.sp) 425 + } 426 + 427 + DropdownMenu( 428 + expanded = menuExpanded, 429 + onDismissRequest = { menuExpanded = false } 430 + ) { 431 + // Zoom toggle 432 + DropdownMenuItem( 433 + text = { 434 + Row { 435 + Text(text = if(ultrawide) "0.5x zoom" else "1.0x zoom") 436 + Spacer(Modifier.width(12.dp)) 437 + Switch(checked = ultrawide, onCheckedChange = null) 438 + } 439 + }, 440 + onClick = { ultrawide = !ultrawide } 441 + ) 442 + 443 + // Preview fill/crop toggle 444 + DropdownMenuItem( 445 + text = { 446 + Row { 447 + Text(text = if(previewFillMode==PreviewFillMode.FIT)"Fill Preview" else "Fit Preview") 448 + Spacer(Modifier.width(12.dp)) 449 + Switch(checked = previewFillMode==PreviewFillMode.FIT, onCheckedChange = null) 450 + } 451 + }, 452 + onClick = { 453 + previewFillMode = if(previewFillMode==PreviewFillMode.FIT) PreviewFillMode.FILL else PreviewFillMode.FIT 454 + } 455 + ) 456 + 457 + HorizontalDivider() 458 + 459 + // Start/End recording button 460 + DropdownMenuItem( 461 + text = { Text(if (recordingId!=null) "End recording" else "Start recording") }, 462 + onClick = { 463 + if(recordingId == null){ 464 + recordingId = "${Clock.System.now().epochSeconds}" 465 + }else{ 466 + recordingId = null 467 + } 468 + // Keep menu open or close? Close feels better. 469 + menuExpanded = false 470 + } 471 + ) 472 + } 473 + } 399 474 } 400 475 } 476 + 401 477 } else Text("Camera permission not granted") 478 + 402 479 val upRightPose = Pose( 403 480 leftShoulder = Pose.PoseRange(0.0, 40.0), 404 481 rightShoulder = Pose.PoseRange(0.0, 40.0),
+25
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/DetectOrientation.kt
··· 1 + package com.nate.posedetection 2 + 3 + import androidx.compose.foundation.layout.BoxWithConstraints 4 + import androidx.compose.runtime.Composable 5 + import androidx.compose.runtime.mutableStateOf 6 + import androidx.compose.runtime.remember 7 + 8 + enum class Orientation { 9 + PORTRAIT, LANDSCAPE 10 + } 11 + 12 + @Composable 13 + fun DetectOrientation(content: @Composable (orientation: Orientation) -> Unit) { 14 + BoxWithConstraints { 15 + val orientation = if (maxWidth > maxHeight) Orientation.LANDSCAPE else Orientation.PORTRAIT 16 + val currentOrientation = remember { mutableStateOf(orientation) } 17 + 18 + // Update if orientation changes 19 + if (currentOrientation.value != orientation) { 20 + currentOrientation.value = orientation 21 + } 22 + 23 + content(currentOrientation.value) 24 + } 25 + }