• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.jetpackcamera.settings
17 
18 import android.Manifest
19 import android.util.Log
20 import androidx.lifecycle.ViewModel
21 import androidx.lifecycle.viewModelScope
22 import com.google.accompanist.permissions.ExperimentalPermissionsApi
23 import com.google.accompanist.permissions.MultiplePermissionsState
24 import com.google.accompanist.permissions.isGranted
25 import com.google.jetpackcamera.settings.DisabledRationale.DeviceUnsupportedRationale
26 import com.google.jetpackcamera.settings.DisabledRationale.FpsUnsupportedRationale
27 import com.google.jetpackcamera.settings.DisabledRationale.StabilizationUnsupportedRationale
28 import com.google.jetpackcamera.settings.model.AspectRatio
29 import com.google.jetpackcamera.settings.model.CameraAppSettings
30 import com.google.jetpackcamera.settings.model.CameraConstraints
31 import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_15
32 import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_30
33 import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_60
34 import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_AUTO
35 import com.google.jetpackcamera.settings.model.DarkMode
36 import com.google.jetpackcamera.settings.model.DynamicRange
37 import com.google.jetpackcamera.settings.model.FlashMode
38 import com.google.jetpackcamera.settings.model.LensFacing
39 import com.google.jetpackcamera.settings.model.StabilizationMode
40 import com.google.jetpackcamera.settings.model.StreamConfig
41 import com.google.jetpackcamera.settings.model.SystemConstraints
42 import com.google.jetpackcamera.settings.model.VideoQuality
43 import com.google.jetpackcamera.settings.model.forCurrentLens
44 import com.google.jetpackcamera.settings.model.forDevice
45 import dagger.hilt.android.lifecycle.HiltViewModel
46 import javax.inject.Inject
47 import kotlinx.coroutines.flow.MutableStateFlow
48 import kotlinx.coroutines.flow.SharingStarted
49 import kotlinx.coroutines.flow.StateFlow
50 import kotlinx.coroutines.flow.combine
51 import kotlinx.coroutines.flow.filterNotNull
52 import kotlinx.coroutines.flow.stateIn
53 import kotlinx.coroutines.flow.update
54 import kotlinx.coroutines.launch
55 
56 private const val TAG = "SettingsViewModel"
57 private val fpsOptions = setOf(FPS_15, FPS_30, FPS_60)
58 
59 /**
60  * [ViewModel] for [SettingsScreen].
61  */
62 @HiltViewModel
63 class SettingsViewModel @Inject constructor(
64     private val settingsRepository: SettingsRepository,
65     constraintsRepository: ConstraintsRepository
66 ) : ViewModel() {
67     private var grantedPermissions = MutableStateFlow<Set<String>>(emptySet())
68 
69     val settingsUiState: StateFlow<SettingsUiState> =
70         combine(
71             settingsRepository.defaultCameraAppSettings,
72             constraintsRepository.systemConstraints.filterNotNull(),
73             grantedPermissions
74         ) { updatedSettings, constraints, grantedPerms ->
75             updatedSettings.videoQuality
76             SettingsUiState.Enabled(
77                 aspectRatioUiState = AspectRatioUiState.Enabled(updatedSettings.aspectRatio),
78                 streamConfigUiState = StreamConfigUiState.Enabled(updatedSettings.streamConfig),
79                 maxVideoDurationUiState = MaxVideoDurationUiState.Enabled(
80                     updatedSettings.maxVideoDurationMillis
81                 ),
82                 flashUiState = getFlashUiState(updatedSettings, constraints),
83                 darkModeUiState = DarkModeUiState.Enabled(updatedSettings.darkMode),
84                 audioUiState = getAudioUiState(
85                     updatedSettings.audioEnabled,
86                     grantedPerms.contains(Manifest.permission.RECORD_AUDIO)
87                 ),
88                 fpsUiState = getFpsUiState(constraints, updatedSettings),
89                 lensFlipUiState = getLensFlipUiState(constraints, updatedSettings),
90                 stabilizationUiState = getStabilizationUiState(constraints, updatedSettings),
91                 videoQualityUiState = getVideoQualityUiState(constraints, updatedSettings)
92             )
93         }.stateIn(
94             scope = viewModelScope,
95             started = SharingStarted.WhileSubscribed(5_000),
96             initialValue = SettingsUiState.Disabled
97         )
98 
99 // ////////////////////////////////////////////////////////////
100 //
101 // Get UiStates for components
102 //
103 // ////////////////////////////////////////////////////////////
104 
105     private fun getFlashUiState(
106         cameraAppSettings: CameraAppSettings,
107         constraints: SystemConstraints
108     ): FlashUiState {
109         val currentSupportedFlashModes =
110             constraints.forCurrentLens(cameraAppSettings)?.supportedFlashModes ?: emptySet()
111 
112         check(currentSupportedFlashModes.isNotEmpty()) {
113             "No flash modes supported. Should at least support OFF."
114         }
115         val deviceSupportedFlashModes: Set<FlashMode> = constraints.forDevice(
116             CameraConstraints::supportedFlashModes
117         )
118         // disable entire setting when:git status
119         //  device only supports off... device unsupported rationale
120         //  lens only supports off... lens unsupported rationale
121         if (deviceSupportedFlashModes == setOf(FlashMode.OFF)) {
122             return FlashUiState.Disabled(
123                 DeviceUnsupportedRationale(R.string.flash_rationale_prefix)
124             )
125         } else if (deviceSupportedFlashModes == setOf(FlashMode.OFF)) {
126             return FlashUiState.Disabled(
127                 getLensUnsupportedRationale(
128                     cameraAppSettings.cameraLensFacing,
129                     R.string.flash_rationale_prefix
130                 )
131             )
132         }
133 
134         // if options besides off are available for this lens...
135         val onSelectableState = if (currentSupportedFlashModes.contains(FlashMode.ON)) {
136             SingleSelectableState.Selectable
137         } else if (deviceSupportedFlashModes.contains(FlashMode.ON)) {
138             SingleSelectableState.Disabled(
139                 getLensUnsupportedRationale(
140                     cameraAppSettings.cameraLensFacing,
141                     affectedSettingNameResId = R.string.flash_on_rationale_prefix
142                 )
143             )
144         } else {
145             SingleSelectableState.Disabled(
146                 DeviceUnsupportedRationale(R.string.flash_on_rationale_prefix)
147             )
148         }
149 
150         val autoSelectableState = if (currentSupportedFlashModes.contains(FlashMode.AUTO)) {
151             SingleSelectableState.Selectable
152         } else if (deviceSupportedFlashModes.contains(FlashMode.AUTO)) {
153             SingleSelectableState.Disabled(
154                 getLensUnsupportedRationale(
155                     cameraAppSettings.cameraLensFacing,
156                     affectedSettingNameResId = R.string.flash_auto_rationale_prefix
157                 )
158             )
159         } else {
160             SingleSelectableState.Disabled(
161                 DeviceUnsupportedRationale(R.string.flash_auto_rationale_prefix)
162             )
163         }
164 
165         // check if llb constraints:
166         // llb must be supported by device
167         val llbSelectableState =
168             if (!currentSupportedFlashModes.contains(FlashMode.LOW_LIGHT_BOOST)) {
169                 SingleSelectableState.Disabled(
170                     DeviceUnsupportedRationale(R.string.flash_llb_rationale_prefix)
171                 )
172             } // llb unsupported above 30fps
173             else if (cameraAppSettings.targetFrameRate > FPS_30) {
174                 SingleSelectableState.Disabled(
175                     FpsUnsupportedRationale(
176                         R.string.flash_llb_rationale_prefix,
177                         cameraAppSettings.targetFrameRate
178                     )
179                 )
180             } else {
181                 SingleSelectableState.Selectable
182             }
183 
184         return FlashUiState.Enabled(
185             currentFlashMode = cameraAppSettings.flashMode,
186             onSelectableState = onSelectableState,
187             autoSelectableState = autoSelectableState,
188             lowLightSelectableState = llbSelectableState
189         )
190     }
191 
192     private fun getAudioUiState(isAudioEnabled: Boolean, permissionGranted: Boolean): AudioUiState =
193         if (permissionGranted) {
194             if (isAudioEnabled) {
195                 AudioUiState.Enabled.On()
196             } else {
197                 AudioUiState.Enabled.Mute()
198             }
199         } else {
200             AudioUiState.Disabled(
201                 DisabledRationale
202                     .PermissionRecordAudioNotGrantedRationale(
203                         R.string.mute_audio_rationale_prefix
204                     )
205             )
206         }
207 
208     @OptIn(ExperimentalPermissionsApi::class)
209     fun setGrantedPermissions(multiplePermissionsState: MultiplePermissionsState) {
210         val permissions = mutableSetOf<String>()
211         for (permissionState in multiplePermissionsState.permissions) {
212             if (permissionState.status.isGranted) {
213                 permissions.add(permissionState.permission)
214             }
215         }
216         grantedPermissions.update {
217             permissions
218         }
219     }
220 
221     fun setGrantedPermissions(permissions: MutableSet<String>) {
222         grantedPermissions.update { permissions }
223     }
224 
225     private fun getStabilizationUiState(
226         systemConstraints: SystemConstraints,
227         cameraAppSettings: CameraAppSettings
228     ): StabilizationUiState {
229         val deviceStabilizations: Set<StabilizationMode> =
230             systemConstraints
231                 .perLensConstraints.values
232                 .asSequence()
233                 .flatMap { it.supportedStabilizationModes }
234                 .toSet()
235 
236         fun supportsStabilization(stabilizationModes: Collection<StabilizationMode>): Boolean =
237             stabilizationModes.isNotEmpty() &&
238                 stabilizationModes.toSet() != setOf(StabilizationMode.OFF)
239 
240         // if no lens supports stabilization
241         if (!supportsStabilization(deviceStabilizations)) {
242             return StabilizationUiState.Disabled(
243                 DeviceUnsupportedRationale(
244                     R.string.stabilization_rationale_prefix
245                 )
246             )
247         }
248 
249         // if a lens supports any stabilization but it isn't the current
250         val currentLensConstraints = checkNotNull(
251             systemConstraints.forCurrentLens(cameraAppSettings)
252         ) {
253             "Lens constraints for ${cameraAppSettings.cameraLensFacing} not available."
254         }
255 
256         with(currentLensConstraints) {
257             supportedStabilizationModes.let {
258                 if (!supportsStabilization(it)) {
259                     return StabilizationUiState.Disabled(
260                         getLensUnsupportedRationale(
261                             cameraAppSettings.cameraLensFacing,
262                             R.string.stabilization_rationale_prefix
263                         )
264                     )
265                 }
266             }
267 
268             // if fps is too high for any stabilization
269             val maxCommonUnsupportedFps = currentLensConstraints.unsupportedStabilizationFpsMap
270                 .asSequence()
271                 .filter {
272                     it.key != StabilizationMode.AUTO &&
273                         it.key != StabilizationMode.OFF &&
274                         it.key in currentLensConstraints.supportedStabilizationModes
275                 }
276                 .map { it.value }
277                 .reduceOrNull { acc, additionalUnsupported -> additionalUnsupported intersect acc }
278                 ?.maxOrNull()
279 
280             if (maxCommonUnsupportedFps != null &&
281                 maxCommonUnsupportedFps <= cameraAppSettings.targetFrameRate
282             ) {
283                 return StabilizationUiState.Disabled(
284                     FpsUnsupportedRationale(
285                         R.string.stabilization_rationale_prefix,
286                         maxCommonUnsupportedFps
287                     )
288                 )
289             }
290 
291             return StabilizationUiState.Enabled(
292                 currentStabilizationMode = cameraAppSettings.stabilizationMode,
293                 stabilizationAutoState = getSingleStabilizationState(
294                     stabilizationMode = StabilizationMode.AUTO,
295                     currentFrameRate = cameraAppSettings.targetFrameRate,
296                     defaultLensFacing = cameraAppSettings.cameraLensFacing,
297                     deviceStabilizations = deviceStabilizations,
298                     currentLensStabilizations = supportedStabilizationModes,
299                     unsupportedFrameRates = StabilizationMode.AUTO.unsupportedFpsSet
300                 ),
301                 stabilizationOnState = getSingleStabilizationState(
302                     stabilizationMode = StabilizationMode.ON,
303                     currentFrameRate = cameraAppSettings.targetFrameRate,
304                     defaultLensFacing = cameraAppSettings.cameraLensFacing,
305                     deviceStabilizations = deviceStabilizations,
306                     currentLensStabilizations = supportedStabilizationModes,
307                     unsupportedFrameRates = StabilizationMode.ON.unsupportedFpsSet
308                 ),
309                 stabilizationHighQualityState = getSingleStabilizationState(
310                     stabilizationMode = StabilizationMode.HIGH_QUALITY,
311                     currentFrameRate = cameraAppSettings.targetFrameRate,
312                     defaultLensFacing = cameraAppSettings.cameraLensFacing,
313                     deviceStabilizations = deviceStabilizations,
314                     currentLensStabilizations = supportedStabilizationModes,
315                     unsupportedFrameRates = StabilizationMode.HIGH_QUALITY.unsupportedFpsSet
316                 ),
317                 stabilizationOpticalState = getSingleStabilizationState(
318                     stabilizationMode = StabilizationMode.OPTICAL,
319                     currentFrameRate = cameraAppSettings.targetFrameRate,
320                     defaultLensFacing = cameraAppSettings.cameraLensFacing,
321                     deviceStabilizations = deviceStabilizations,
322                     currentLensStabilizations = supportedStabilizationModes,
323                     unsupportedFrameRates = StabilizationMode.OPTICAL.unsupportedFpsSet
324                 )
325             )
326         }
327     }
328 
329     private fun getSingleStabilizationState(
330         stabilizationMode: StabilizationMode,
331         currentFrameRate: Int,
332         defaultLensFacing: LensFacing,
333         deviceStabilizations: Set<StabilizationMode>,
334         currentLensStabilizations: Set<StabilizationMode>?,
335         unsupportedFrameRates: Set<Int>
336     ): SingleSelectableState {
337         // if unsupported by device
338         if (!deviceStabilizations.contains(stabilizationMode)) {
339             return SingleSelectableState.Disabled(
340                 disabledRationale =
341                 DeviceUnsupportedRationale(R.string.stabilization_rationale_prefix)
342             )
343         }
344 
345         // if unsupported by by current lens
346         if (currentLensStabilizations?.contains(stabilizationMode) == false) {
347             return SingleSelectableState.Disabled(
348                 getLensUnsupportedRationale(
349                     defaultLensFacing,
350                     R.string.stabilization_rationale_prefix
351                 )
352             )
353         }
354 
355         // if fps is unsupported by preview stabilization
356         if (currentFrameRate in unsupportedFrameRates) {
357             return SingleSelectableState.Disabled(
358                 FpsUnsupportedRationale(
359                     R.string.stabilization_rationale_prefix,
360                     currentFrameRate
361                 )
362             )
363         }
364 
365         return SingleSelectableState.Selectable
366     }
367 
368     private fun getVideoQualityUiState(
369         systemConstraints: SystemConstraints,
370         cameraAppSettings: CameraAppSettings
371     ): VideoQualityUiState {
372         val cameraConstraints = systemConstraints.forCurrentLens(cameraAppSettings)
373         val supportedVideoQualities: List<VideoQuality> =
374             cameraConstraints?.supportedVideoQualitiesMap?.get(
375                 cameraAppSettings.dynamicRange
376             ) ?: listOf(VideoQuality.UNSPECIFIED)
377 
378         return if (supportedVideoQualities != listOf(VideoQuality.UNSPECIFIED)) {
379             VideoQualityUiState.Enabled(
380                 currentVideoQuality = cameraAppSettings.videoQuality,
381                 videoQualityAutoState = SingleSelectableState.Selectable,
382                 videoQualitySDState = getSingleVideoQualityState(
383                     VideoQuality.SD,
384                     supportedVideoQualities
385                 ),
386                 videoQualityHDState = getSingleVideoQualityState(
387                     VideoQuality.HD,
388                     supportedVideoQualities
389                 ),
390                 videoQualityFHDState = getSingleVideoQualityState(
391                     VideoQuality.FHD,
392                     supportedVideoQualities
393                 ),
394                 videoQualityUHDState = getSingleVideoQualityState(
395                     VideoQuality.UHD,
396                     supportedVideoQualities
397                 )
398             )
399         } else {
400             VideoQualityUiState.Disabled(
401                 DisabledRationale.VideoQualityUnsupportedRationale(
402                     R.string.video_quality_rationale_prefix
403                 )
404             )
405         }
406     }
407 
408     private fun getSingleVideoQualityState(
409         videoQuality: VideoQuality,
410         supportedVideQualities: List<VideoQuality>
411     ): SingleSelectableState = if (supportedVideQualities.contains(videoQuality)) {
412         SingleSelectableState.Selectable
413     } else {
414         SingleSelectableState.Disabled(
415             DisabledRationale.VideoQualityUnsupportedRationale(
416                 R.string.video_quality_rationale_prefix
417             )
418         )
419     }
420 
421     /**
422      * Enables or disables default camera switch based on:
423      * - number of cameras available
424      * - if there is a front and rear camera, the camera that the setting would switch to must also
425      * support the other settings
426      * */
427     private fun getLensFlipUiState(
428         systemConstraints: SystemConstraints,
429         currentSettings: CameraAppSettings
430     ): FlipLensUiState {
431         // if there is only one lens, stop here
432         if (!with(systemConstraints.availableLenses) {
433                 size > 1 && contains(com.google.jetpackcamera.settings.model.LensFacing.FRONT)
434             }
435         ) {
436             return FlipLensUiState.Disabled(
437                 currentLensFacing = currentSettings.cameraLensFacing,
438                 disabledRationale =
439                 DeviceUnsupportedRationale(
440                     // display the lens that isnt supported
441                     when (currentSettings.cameraLensFacing) {
442                         LensFacing.BACK -> R.string.front_lens_rationale_prefix
443                         LensFacing.FRONT -> R.string.rear_lens_rationale_prefix
444                     }
445                 )
446             )
447         }
448 
449         // If multiple lens available, continue
450         val newLensFacing = if (currentSettings.cameraLensFacing == LensFacing.FRONT) {
451             LensFacing.BACK
452         } else {
453             LensFacing.FRONT
454         }
455         val newLensConstraints = systemConstraints.perLensConstraints[newLensFacing]!!
456         // make sure all current settings wont break constraint when changing new default lens
457 
458         // if new lens won't support current fps
459         if (currentSettings.targetFrameRate != FPS_AUTO &&
460             !newLensConstraints.supportedFixedFrameRates
461                 .contains(currentSettings.targetFrameRate)
462         ) {
463             return FlipLensUiState.Disabled(
464                 currentLensFacing = currentSettings.cameraLensFacing,
465                 disabledRationale = FpsUnsupportedRationale(
466                     when (currentSettings.cameraLensFacing) {
467                         LensFacing.BACK -> R.string.front_lens_rationale_prefix
468                         LensFacing.FRONT -> R.string.rear_lens_rationale_prefix
469                     },
470                     currentSettings.targetFrameRate
471                 )
472             )
473         }
474 
475         // If a non-AUTO stabilization is currently on and the other lens won't support it
476         if (currentSettings.stabilizationMode != StabilizationMode.AUTO &&
477             currentSettings.stabilizationMode !in newLensConstraints.supportedStabilizationModes
478         ) {
479             return FlipLensUiState.Disabled(
480                 currentLensFacing = currentSettings.cameraLensFacing,
481                 disabledRationale = StabilizationUnsupportedRationale(
482                     when (currentSettings.cameraLensFacing) {
483                         LensFacing.BACK -> R.string.front_lens_rationale_prefix
484                         LensFacing.FRONT -> R.string.rear_lens_rationale_prefix
485                     }
486                 )
487             )
488         }
489 
490         // if other lens doesnt support the video quality
491         if (currentSettings.videoQuality != VideoQuality.UNSPECIFIED &&
492             newLensConstraints.supportedVideoQualitiesMap[DynamicRange.SDR]?.contains(
493                 currentSettings.videoQuality
494             ) != true
495         ) {
496             return FlipLensUiState.Disabled(
497                 currentLensFacing = currentSettings.cameraLensFacing,
498                 disabledRationale = DisabledRationale.VideoQualityUnsupportedRationale(
499                     when (currentSettings.cameraLensFacing) {
500                         LensFacing.BACK -> R.string.front_lens_rationale_prefix
501                         LensFacing.FRONT -> R.string.rear_lens_rationale_prefix
502                     },
503                     R.string.video_quality_rationale_suffix_sdr
504                 )
505             )
506         }
507 
508         return FlipLensUiState.Enabled(currentLensFacing = currentSettings.cameraLensFacing)
509     }
510 
511     private fun getFpsUiState(
512         systemConstraints: SystemConstraints,
513         cameraAppSettings: CameraAppSettings
514     ): FpsUiState {
515         val optionConstraintRationale: MutableMap<Int, SingleSelectableState> = mutableMapOf()
516 
517         val deviceSupportedFrameRates = systemConstraints.perLensConstraints
518             .asSequence()
519             .flatMap { it.value.supportedFixedFrameRates }
520             .toSet()
521 
522         // if device supports no fixed frame rates, disable
523         if (deviceSupportedFrameRates.isEmpty()) {
524             return FpsUiState.Disabled(
525                 DeviceUnsupportedRationale(R.string.no_fixed_fps_rationale_prefix)
526             )
527         }
528 
529         val currentLensConstraints = checkNotNull(
530             systemConstraints.forCurrentLens(cameraAppSettings)
531         ) {
532             "Lens constraints for ${cameraAppSettings.cameraLensFacing} not available."
533         }
534 
535         with(currentLensConstraints) {
536             // provide selectable states for each of the fps options
537             fpsOptions.forEach { fpsOption ->
538                 val fpsUiState = isFpsOptionEnabled(
539                     fpsOption = fpsOption,
540                     defaultLensFacing = cameraAppSettings.cameraLensFacing,
541                     deviceSupportedFrameRates = deviceSupportedFrameRates,
542                     stabilizationMode = cameraAppSettings.stabilizationMode
543                 )
544                 if (fpsUiState is SingleSelectableState.Disabled) {
545                     Log.d(
546                         TAG,
547                         "fps option $fpsOption disabled. ${fpsUiState.disabledRationale::class}"
548                     )
549                 }
550                 optionConstraintRationale[fpsOption] = fpsUiState
551             }
552         }
553         return FpsUiState.Enabled(
554             currentSelection = cameraAppSettings.targetFrameRate,
555             fpsAutoState = SingleSelectableState.Selectable,
556             fpsFifteenState = optionConstraintRationale[FPS_15]!!,
557             fpsThirtyState = optionConstraintRationale[FPS_30]!!,
558             fpsSixtyState = optionConstraintRationale[FPS_60]!!
559         )
560     }
561 
562     /**
563      * Auxiliary function to determine if an FPS option should be disabled or not
564      */
565     private fun CameraConstraints.isFpsOptionEnabled(
566         fpsOption: Int,
567         defaultLensFacing: LensFacing,
568         deviceSupportedFrameRates: Set<Int>,
569         stabilizationMode: StabilizationMode
570     ): SingleSelectableState {
571         // if device doesn't support the fps option, disable
572         if (!deviceSupportedFrameRates.contains(fpsOption)) {
573             return SingleSelectableState.Disabled(
574                 disabledRationale = DeviceUnsupportedRationale(R.string.fps_rationale_prefix)
575             )
576         }
577         // if the current lens doesn't support the fps, disable
578         if (!supportedFixedFrameRates.contains(fpsOption)) {
579             Log.d(TAG, "FPS disabled for current lens")
580 
581             return SingleSelectableState.Disabled(
582                 getLensUnsupportedRationale(defaultLensFacing, R.string.fps_rationale_prefix)
583             )
584         }
585 
586         // if stabilization is on and the option is incompatible, disable
587         if (fpsOption in stabilizationMode.unsupportedFpsSet) {
588             return SingleSelectableState.Disabled(
589                 StabilizationUnsupportedRationale(R.string.fps_rationale_prefix)
590             )
591         }
592 
593         return SingleSelectableState.Selectable
594     }
595 
596 // ////////////////////////////////////////////////////////////
597 //
598 // Settings Repository functions
599 //
600 // ////////////////////////////////////////////////////////////
601 
602     fun setDefaultLensFacing(lensFacing: LensFacing) {
603         viewModelScope.launch {
604             settingsRepository.updateDefaultLensFacing(lensFacing)
605             Log.d(TAG, "set camera default facing: $lensFacing")
606         }
607     }
608 
609     fun setDarkMode(darkMode: DarkMode) {
610         viewModelScope.launch {
611             settingsRepository.updateDarkModeStatus(darkMode)
612             Log.d(TAG, "set dark mode theme: $darkMode")
613         }
614     }
615 
616     fun setFlashMode(flashMode: FlashMode) {
617         viewModelScope.launch {
618             settingsRepository.updateFlashModeStatus(flashMode)
619             Log.d(TAG, "set flash mode: $flashMode")
620         }
621     }
622 
623     fun setTargetFrameRate(targetFrameRate: Int) {
624         viewModelScope.launch {
625             settingsRepository.updateTargetFrameRate(targetFrameRate)
626             Log.d(TAG, "set target frame rate: $targetFrameRate")
627         }
628     }
629 
630     fun setAspectRatio(aspectRatio: AspectRatio) {
631         viewModelScope.launch {
632             settingsRepository.updateAspectRatio(aspectRatio)
633             Log.d(TAG, "set aspect ratio: $aspectRatio")
634         }
635     }
636 
637     fun setStreamConfig(streamConfig: StreamConfig) {
638         viewModelScope.launch {
639             settingsRepository.updateStreamConfig(streamConfig)
640             Log.d(TAG, "set default capture mode: $streamConfig")
641         }
642     }
643 
644     fun setStabilizationMode(stabilizationMode: StabilizationMode) {
645         viewModelScope.launch {
646             settingsRepository.updateStabilizationMode(stabilizationMode)
647             Log.d(TAG, "set stabilization mode: $stabilizationMode")
648         }
649     }
650 
651     fun setMaxVideoDuration(durationMillis: Long) {
652         viewModelScope.launch {
653             settingsRepository.updateMaxVideoDuration(durationMillis)
654             Log.d(TAG, "set video duration: $durationMillis ms")
655         }
656     }
657 
658     fun setVideoQuality(videoQuality: VideoQuality) {
659         viewModelScope.launch {
660             settingsRepository.updateVideoQuality(videoQuality)
661             Log.d(TAG, "set video quality: $videoQuality ms")
662         }
663     }
664 
665     fun setVideoAudio(isAudioEnabled: Boolean) {
666         viewModelScope.launch {
667             settingsRepository.updateAudioEnabled(isAudioEnabled)
668             Log.d(TAG, "recording audio muted: $isAudioEnabled")
669         }
670     }
671 }
672