diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-11-01 13:48:23 +0300 |
---|---|---|
committer | Stefan Niedermann <info@niedermann.it> | 2020-11-01 13:48:23 +0300 |
commit | 7ebc17313f689281637cf765cba3a8b4b9e7fefe (patch) | |
tree | 946b06d3400d0a9202463cfda4bfadd9538e6488 /app | |
parent | a6839a01fff904beb5394661591a4cbf84bdfa75 (diff) |
Allow to toggle camera & flashlight
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'app')
11 files changed, 224 insertions, 43 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 34b49286e..f936fac67 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ <uses-feature android:name="android.hardware.camera.any" /> - <uses-sdk tools:overrideLibrary="androidx.camera.core, androidx.camera.camera2, androidx.camera.lifecycle, androidx.camera.view"/> + <uses-sdk tools:overrideLibrary="androidx.camera.core, androidx.camera.camera2, androidx.camera.lifecycle, androidx.camera.view" /> <application android:name="it.niedermann.nextcloud.deck.DeckApplication" @@ -57,7 +57,7 @@ <activity android:name=".ui.takephoto.TakePhotoActivity" - android:theme="@style/TransparentTheme" + android:theme="@style/TakePhotoTheme" android:windowSoftInputMode="stateHidden" /> <activity diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java index 4db77788b..af17464dc 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java @@ -6,6 +6,9 @@ import android.content.res.ColorStateList; import android.net.Uri; import android.os.Bundle; import android.util.Size; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,6 +19,7 @@ import androidx.camera.core.ImageCaptureException; import androidx.camera.core.Preview; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; import com.google.common.util.concurrent.ListenableFuture; @@ -33,15 +37,18 @@ import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler; import it.niedermann.nextcloud.deck.util.AttachmentUtil; import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA; import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.IMAGE_JPEG; @RequiresApi(LOLLIPOP) public class TakePhotoActivity extends BrandedActivity { private ActivityTakePhotoBinding binding; + private TakePhotoViewModel viewModel; + + private View[] brandedViews; private ListenableFuture<ProcessCameraProvider> cameraProviderFuture; + private OrientationEventListener orientationEventListener; private final DateTimeFormatter fileNameFromCameraFormatter = DateTimeFormatter.ofPattern("'JPG_'yyyyMMdd'_'HHmmss'.jpg'"); @@ -52,51 +59,112 @@ public class TakePhotoActivity extends BrandedActivity { Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this)); binding = ActivityTakePhotoBinding.inflate(getLayoutInflater()); + viewModel = new ViewModelProvider(this).get(TakePhotoViewModel.class); + setContentView(binding.getRoot()); cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { final ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); - - final Preview previewUseCase = new Preview.Builder().build(); - previewUseCase.setSurfaceProvider(binding.preview.getSurfaceProvider()); - - final ImageCapture captureUseCase = new ImageCapture.Builder().setTargetResolution(new Size(720, 1280)).build(); - - binding.takePhoto.setOnClickListener((v) -> { - binding.takePhoto.setEnabled(false); - final String photoFileName = Instant.now().atZone(ZoneId.systemDefault()).format(fileNameFromCameraFormatter); - try { - final File photoFile = AttachmentUtil.getTempCacheFile(this, "photos/" + photoFileName); - final ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(photoFile).build(); - captureUseCase.takePicture(options, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() { - @Override - public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { - final Uri savedUri = Uri.fromFile(photoFile); - DeckLog.info("onImageSaved - savedUri: " + savedUri.toString()); - setResult(RESULT_OK, new Intent().setDataAndType(savedUri, IMAGE_JPEG)); - finish(); - } - - @Override - public void onError(@NonNull ImageCaptureException e) { - e.printStackTrace(); - //noinspection ResultOfMethodCallIgnored - photoFile.delete(); - binding.takePhoto.setEnabled(true); - } - }); - } catch (Exception e) { - ExceptionDialogFragment.newInstance(e, null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); - } + final Preview previewUseCase = getPreviewUseCase(); + final ImageCapture captureUseCase = getCaptureUseCase(); + final Camera camera = cameraProvider.bindToLifecycle(this, viewModel.getCameraSelector(), captureUseCase, previewUseCase); + + viewModel.getCameraSelectorToggleButtonImageResource().observe(this, res -> binding.switchCamera.setImageDrawable(ContextCompat.getDrawable(this, res))); + viewModel.getTorchToggleButtonImageResource().observe(this, res -> binding.toggleTorch.setImageDrawable(ContextCompat.getDrawable(this, res))); + viewModel.isTorchEnabled().observe(this, enabled -> camera.getCameraControl().enableTorch(enabled)); + + binding.toggleTorch.setOnClickListener((v) -> viewModel.toggleTorchEnabled()); + binding.switchCamera.setOnClickListener((v) -> { + viewModel.toggleCameraSelector(); + cameraProvider.unbindAll(); + cameraProvider.bindToLifecycle(this, viewModel.getCameraSelector(), captureUseCase, previewUseCase); }); - Camera camera = cameraProvider.bindToLifecycle(this, DEFAULT_BACK_CAMERA, captureUseCase, previewUseCase); } catch (ExecutionException | InterruptedException e) { DeckLog.logError(e); + finish(); } - }, ContextCompat.getMainExecutor(this)); + + brandedViews = new View[]{binding.takePhoto, binding.switchCamera, binding.toggleTorch}; + } + + private ImageCapture getCaptureUseCase() { + final ImageCapture captureUseCase = new ImageCapture.Builder().setTargetResolution(new Size(720, 1280)).build(); + + orientationEventListener = new OrientationEventListener(this) { + @Override + public void onOrientationChanged(int orientation) { + int rotation; + + // Monitors orientation values to determine the target rotation value + if (orientation >= 45 && orientation < 135) { + rotation = Surface.ROTATION_270; + } else if (orientation >= 135 && orientation < 225) { + rotation = Surface.ROTATION_180; + } else if (orientation >= 225 && orientation < 315) { + rotation = Surface.ROTATION_90; + } else { + rotation = Surface.ROTATION_0; + } + + captureUseCase.setTargetRotation(rotation); + } + }; + orientationEventListener.enable(); + + binding.takePhoto.setOnClickListener((v) -> { + binding.takePhoto.setEnabled(false); + final String photoFileName = Instant.now().atZone(ZoneId.systemDefault()).format(fileNameFromCameraFormatter); + try { + final File photoFile = AttachmentUtil.getTempCacheFile(this, "photos/" + photoFileName); + final ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(photoFile).build(); + captureUseCase.takePicture(options, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() { + @Override + public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { + final Uri savedUri = Uri.fromFile(photoFile); + DeckLog.info("onImageSaved - savedUri: " + savedUri.toString()); + setResult(RESULT_OK, new Intent().setDataAndType(savedUri, IMAGE_JPEG)); + finish(); + } + + @Override + public void onError(@NonNull ImageCaptureException e) { + e.printStackTrace(); + //noinspection ResultOfMethodCallIgnored + photoFile.delete(); + binding.takePhoto.setEnabled(true); + } + }); + } catch (Exception e) { + ExceptionDialogFragment.newInstance(e, null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + } + }); + + return captureUseCase; + } + + private Preview getPreviewUseCase() { + Preview previewUseCase = new Preview.Builder().build(); + previewUseCase.setSurfaceProvider(binding.preview.getSurfaceProvider()); + return previewUseCase; + } + + @Override + protected void onPause() { + if (this.orientationEventListener != null) { + this.orientationEventListener.disable(); + } + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + if (this.orientationEventListener != null) { + this.orientationEventListener.enable(); + } } @RequiresApi(LOLLIPOP) @@ -106,6 +174,9 @@ public class TakePhotoActivity extends BrandedActivity { @Override public void applyBrand(int mainColor) { - binding.takePhoto.setBackgroundTintList(ColorStateList.valueOf(mainColor)); + final ColorStateList colorStateList = ColorStateList.valueOf(mainColor); + for (View v : brandedViews) { + v.setBackgroundTintList(colorStateList); + } } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoViewModel.java new file mode 100644 index 000000000..a71291ff2 --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoViewModel.java @@ -0,0 +1,57 @@ +package it.niedermann.nextcloud.deck.ui.takephoto; + +import androidx.annotation.NonNull; +import androidx.camera.core.CameraSelector; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; + +import it.niedermann.nextcloud.deck.R; + +import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA; +import static androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA; + +public class TakePhotoViewModel extends ViewModel { + + @NonNull + private CameraSelector cameraSelector = DEFAULT_BACK_CAMERA; + @NonNull + private final MutableLiveData<Integer> cameraSelectorToggleButtonImageResource = new MutableLiveData<>(R.drawable.ic_baseline_camera_front_24); + @NonNull + private final MutableLiveData<Boolean> torchEnabled = new MutableLiveData<>(false); + + @NonNull + public CameraSelector getCameraSelector() { + return this.cameraSelector; + } + + public LiveData<Integer> getCameraSelectorToggleButtonImageResource() { + return this.cameraSelectorToggleButtonImageResource; + } + + public void toggleCameraSelector() { + if (this.cameraSelector == DEFAULT_BACK_CAMERA) { + this.cameraSelector = DEFAULT_FRONT_CAMERA; + this.cameraSelectorToggleButtonImageResource.postValue(R.drawable.ic_baseline_camera_rear_24); + } else { + this.cameraSelector = DEFAULT_BACK_CAMERA; + this.cameraSelectorToggleButtonImageResource.postValue(R.drawable.ic_baseline_camera_front_24); + } + } + + public void toggleTorchEnabled() { + //noinspection ConstantConditions + this.torchEnabled.postValue(!this.torchEnabled.getValue()); + } + + public LiveData<Boolean> isTorchEnabled() { + return this.torchEnabled; + } + + public LiveData<Integer> getTorchToggleButtonImageResource() { + return Transformations.map(isTorchEnabled(), enabled -> enabled + ? R.drawable.ic_baseline_flash_off_24 + : R.drawable.ic_baseline_flash_on_24); + } +} diff --git a/app/src/main/res/drawable/ic_baseline_camera_front_24.xml b/app/src/main/res/drawable/ic_baseline_camera_front_24.xml new file mode 100644 index 000000000..25c1a79b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_camera_front_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#757575" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M10,20L5,20v2h5v2l3,-3 -3,-3v2zM14,20v2h5v-2h-5zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -1.99,0.9 -1.99,2S10.9,8 12,8zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM7,2h10v10.5c0,-1.67 -3.33,-2.5 -5,-2.5s-5,0.83 -5,2.5L7,2z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml b/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml new file mode 100644 index 000000000..51cea2177 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#757575" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M10,20L5,20v2h5v2l3,-3 -3,-3v2zM14,20v2h5v-2h-5zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM12,6c-1.11,0 -2,-0.9 -2,-2s0.89,-2 1.99,-2 2,0.9 2,2C14,5.1 13.1,6 12,6z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_baseline_flash_off_24.xml b/app/src/main/res/drawable/ic_baseline_flash_off_24.xml new file mode 100644 index 000000000..2a3b0ff5d --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flash_off_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#757575" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M3.27,3L2,4.27l5,5V13h3v9l3.58,-6.14L17.73,20 19,18.73 3.27,3zM17,10h-4l4,-8H7v2.18l8.46,8.46L17,10z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_baseline_flash_on_24.xml b/app/src/main/res/drawable/ic_baseline_flash_on_24.xml new file mode 100644 index 000000000..4574d0e20 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flash_on_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#757575" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z"/> +</vector> diff --git a/app/src/main/res/layout/activity_take_photo.xml b/app/src/main/res/layout/activity_take_photo.xml index b665aaa33..76d169507 100644 --- a/app/src/main/res/layout/activity_take_photo.xml +++ b/app/src/main/res/layout/activity_take_photo.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@android:color/black" android:orientation="vertical" tools:theme="@style/TransparentTheme"> @@ -12,20 +13,43 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - <LinearLayout + <com.google.android.flexbox.FlexboxLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" - android:gravity="center" - android:padding="@dimen/spacer_3x"> + android:background="@color/camera_controls_overlay" + app:alignItems="center" + app:justifyContent="space_evenly"> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/switchCamera" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/take_photo_switch_camera" + android:tint="@android:color/white" + app:backgroundTint="@color/defaultBrand" + app:fabSize="mini" + tools:srcCompat="@drawable/ic_baseline_camera_front_24" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/takePhoto" + android:layout_marginTop="@dimen/spacer_3x" + android:layout_marginBottom="@dimen/spacer_3x" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:contentDescription="Take photo" + android:contentDescription="@string/take_photo" android:tint="@android:color/white" app:backgroundTint="@color/defaultBrand" app:srcCompat="@drawable/ic_baseline_photo_camera_24" /> - </LinearLayout> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/toggle_torch" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/take_photo_toggle_torch" + android:tint="@android:color/white" + app:backgroundTint="@color/defaultBrand" + app:fabSize="mini" + tools:srcCompat="@drawable/ic_baseline_flash_on_24" /> + </com.google.android.flexbox.FlexboxLayout> </RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 55812677c..f07146467 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -38,4 +38,6 @@ <color name="widget_background">#ccf5f5f5</color> <color name="widget_foreground">#212121</color> + + <color name="camera_controls_overlay">#66000000</color> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9ac8c98e1..b7b50682b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -316,4 +316,6 @@ <string name="simple_camera">Camera</string> <string name="min_api_21">This feature requires at least Android 5</string> <string name="take_photo">Take a photo</string> + <string name="take_photo_switch_camera">Switch camera</string> + <string name="take_photo_toggle_torch">Toggle torch</string> </resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 874d2e751..9881f8e76 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -43,6 +43,11 @@ <item name="android:windowIsTranslucent">true</item> </style> + <style name="TakePhotoTheme" parent="TransparentTheme"> + <item name="android:windowFullscreen">true</item> + <item name="android:windowContentOverlay">@null</item> + </style> + <style name="Deck.TextAppearance.Headline1" parent="TextAppearance.MaterialComponents.Headline1"> <item name="android:textSize">36sp</item> </style> |