diff --git a/app/build.gradle b/app/build.gradle index a54740cd4..eca8da289 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,6 +187,10 @@ dependencies { ktlint "com.pinterest:ktlint:0.43.0" implementation 'org.conscrypt:conscrypt-android:2.5.2' + implementation 'androidx.camera:camera-camera2:1.0.1' + implementation 'androidx.camera:camera-lifecycle:1.0.1' + implementation 'androidx.camera:camera-view:1.0.0-alpha20' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.biometric:biometric:1.1.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1d0260733..d6c2e86bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -160,6 +160,11 @@ android:configChanges="orientation|keyboardHidden|screenSize"> </activity> + <activity + android:name=".activities.TakePhotoActivity" + android:theme="@style/TakePhotoTheme" + android:windowSoftInputMode="stateHidden" /> + <receiver android:name=".receivers.PackageReplacedReceiver"> <intent-filter> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> diff --git a/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java b/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java new file mode 100644 index 000000000..db00dd054 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java @@ -0,0 +1,185 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * @author Stefan Niedermann + * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> + * Copyright (C) 2021 Stefan Niedermann <info@niedermann.it> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.nextcloud.talk.activities; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.util.Size; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.widget.Toast; + +import com.google.common.util.concurrent.ListenableFuture; +import com.nextcloud.talk.databinding.ActivityTakePictureBinding; +import com.nextcloud.talk.models.TakePictureViewModel; +import com.nextcloud.talk.utils.FileUtils; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.ExecutionException; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.camera.core.Camera; +import androidx.camera.core.ImageCapture; +import androidx.camera.core.ImageCaptureException; +import androidx.camera.core.Preview; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; + +public class TakePhotoActivity extends AppCompatActivity { + + private static final String TAG = TakePhotoActivity.class.getSimpleName(); + + private ActivityTakePictureBinding binding; + private TakePictureViewModel viewModel; + + private ListenableFuture<ProcessCameraProvider> cameraProviderFuture; + private OrientationEventListener orientationEventListener; + + private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss", Locale.ROOT); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityTakePictureBinding.inflate(getLayoutInflater()); + viewModel = new ViewModelProvider(this).get(TakePictureViewModel.class); + + setContentView(binding.getRoot()); + + cameraProviderFuture = ProcessCameraProvider.getInstance(this); + cameraProviderFuture.addListener(() -> { + try { + final ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); + final Preview preview = getPreview(); + final ImageCapture imageCapture = getImageCapture(); + final Camera camera = cameraProvider.bindToLifecycle(this, viewModel.getCameraSelector(), imageCapture, preview); + + 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(), imageCapture, preview); + }); + } catch (IllegalArgumentException | ExecutionException | InterruptedException e) { + Log.e(TAG, "Error taking picture", e); + Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); + finish(); + } + }, ContextCompat.getMainExecutor(this)); + } + + private ImageCapture getImageCapture() { + final ImageCapture imageCapture = 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; + } + + imageCapture.setTargetRotation(rotation); + } + }; + orientationEventListener.enable(); + + binding.takePhoto.setOnClickListener((v) -> { + binding.takePhoto.setEnabled(false); + final String photoFileName = dateFormat.format(new Date())+ ".jpg"; + try { + final File photoFile = FileUtils.getTempCacheFile(this, "photos/" + photoFileName); + final ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(photoFile).build(); + imageCapture.takePicture(options, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() { + @Override + public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { + final Uri savedUri = Uri.fromFile(photoFile); + Log.i(TAG, "onImageSaved - savedUri:" + savedUri.toString()); + setResult(RESULT_OK, new Intent().setDataAndType(savedUri, "image/jpeg")); + finish(); + } + + @Override + public void onError(@NonNull ImageCaptureException e) { + Log.e(TAG, "Error", e); + //noinspection ResultOfMethodCallIgnored + photoFile.delete(); + binding.takePhoto.setEnabled(true); + } + }); + } catch (Exception e) { + // TODO replace string with placeholder + Toast.makeText(this, "Error taking picture", Toast.LENGTH_SHORT).show(); + } + }); + + return imageCapture; + } + + private Preview getPreview() { + Preview preview = new Preview.Builder().build(); + preview.setSurfaceProvider(binding.preview.getSurfaceProvider()); + return preview; + } + + @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(); + } + } + + public static Intent createIntent(@NonNull Context context) { + return new Intent(context, TakePhotoActivity.class).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + } +} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 703c21ee0..ae5749e6c 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -96,6 +96,7 @@ import com.google.android.flexbox.FlexboxLayout import com.nextcloud.talk.R import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.MainActivity +import com.nextcloud.talk.activities.TakePhotoActivity import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingVoiceMessageViewHolder @@ -1221,6 +1222,34 @@ class ChatController(args: Bundle) : Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) } } + } else if (requestCode == REQUEST_CODE_PICK_CAMERA) { + if (resultCode == RESULT_OK) { + try { + checkNotNull(intent) + filesToUpload.clear() + run { + checkNotNull(intent.data) + intent.data.let { + filesToUpload.add(intent.data.toString()) + } + } + require(filesToUpload.isNotEmpty()) + + if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) { + uploadFiles(filesToUpload, false) + } else { + UploadAndShareFilesWorker.requestStoragePermission(this) + } + } catch (e: IllegalStateException) { + Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG) + .show() + Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) + } catch (e: IllegalArgumentException) { + Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG) + .show() + Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) + } + } } } @@ -2542,6 +2571,10 @@ class ChatController(args: Bundle) : } } + fun sendPictureFromCamIntent() { + startActivityForResult(TakePhotoActivity.createIntent(context!!), REQUEST_CODE_PICK_CAMERA) + } + companion object { private const val TAG = "ChatController" private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1 @@ -2556,6 +2589,7 @@ class ChatController(args: Bundle) : private const val AGE_THREHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000) private const val REQUEST_CODE_CHOOSE_FILE: Int = 555 private const val REQUEST_RECORD_AUDIO_PERMISSION = 222 + private const val REQUEST_CODE_PICK_CAMERA: Int = 333 private const val OBJECT_MESSAGE: String = "{object}" private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000 private const val VOICE_RECORD_CANCEL_SLIDER_X: Int = -50 diff --git a/app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java b/app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java new file mode 100644 index 000000000..e6874cd65 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java @@ -0,0 +1,80 @@ +/* + * Nextcloud Talk application + * + * @author Andy Scherzinger + * @author Stefan Niedermann + * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> + * Copyright (C) 2021 Stefan Niedermann <info@niedermann.it> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.nextcloud.talk.models; + +import com.nextcloud.talk.R; + +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 static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA; +import static androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA; + +public class TakePictureViewModel 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/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt index 51b2959fb..ed6185bbe 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt @@ -54,6 +54,10 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle @JvmField var attachFromCloud: AppCompatTextView? = null + @BindView(R.id.menu_attach_picture_from_cam) + @JvmField + var pictureFromCamItem: LinearLayout? = null + private var unbinder: Unbinder? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -88,6 +92,12 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle chatController.sendSelectLocalFileIntent() dismiss() } + + pictureFromCamItem?.setOnClickListener { + chatController.sendPictureFromCamIntent() + dismiss() + } + attachFromCloud?.setOnClickListener { chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER) dismiss() diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileUtils.java b/app/src/main/java/com/nextcloud/talk/utils/FileUtils.java new file mode 100644 index 000000000..e18e0147b --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/FileUtils.java @@ -0,0 +1,46 @@ +package com.nextcloud.talk.utils; + +import android.content.Context; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import androidx.annotation.NonNull; + +public class FileUtils { + private static final String TAG = FileUtils.class.getSimpleName(); + + /** + * Creates a new {@link File} + */ + public static File getTempCacheFile(@NonNull Context context, String fileName) throws IOException { + File cacheFile = new File(context.getApplicationContext().getFilesDir().getAbsolutePath() + "/" + fileName); + + Log.v(TAG, "Full path for new cache file:" + cacheFile.getAbsolutePath()); + + final File tempDir = cacheFile.getParentFile(); + if (tempDir == null) { + throw new FileNotFoundException("could not cacheFile.getParentFile()"); + } + if (!tempDir.exists()) { + Log.v(TAG, + "The folder in which the new file should be created does not exist yet. Trying to create it…"); + if (tempDir.mkdirs()) { + Log.v(TAG, "Creation successful"); + } else { + throw new IOException("Directory for temporary file does not exist and could not be created."); + } + } + + Log.v(TAG, "- Try to create actual cache file"); + if (cacheFile.createNewFile()) { + Log.v(TAG, "Successfully created cache file"); + } else { + throw new IOException("Failed to create cacheFile"); + } + + return cacheFile; + } +} 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/drawable/ic_baseline_photo_camera_24.xml b/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml new file mode 100644 index 000000000..497db8383 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml @@ -0,0 +1,6 @@ +<vector android:autoMirrored="true" 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="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> + <path android:fillColor="@android:color/white" android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/> +</vector> diff --git a/app/src/main/res/layout/activity_take_picture.xml b/app/src/main/res/layout/activity_take_picture.xml new file mode 100644 index 000000000..d5daa77cf --- /dev/null +++ b/app/src/main/res/layout/activity_take_picture.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Nextcloud Talk application + ~ + ~ @author Andy Scherzinger + ~ @author Stefan Niedermann + ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> + ~ Copyright (C) 2021 Stefan Niedermann <info@niedermann.it> + ~ + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ at your option) any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + 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"> + + <androidx.camera.view.PreviewView + android:id="@+id/preview" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:background="@color/transparent_black" + android:paddingTop="@dimen/standard_padding" + android:paddingBottom="@dimen/standard_double_padding" + app:layout_constraintBottom_toBottomOf="parent"> + + <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/colorPrimary" + app:fabSize="mini" + app:layout_constraintBottom_toBottomOf="@id/takePhoto" + app:layout_constraintEnd_toStartOf="@id/takePhoto" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/takePhoto" + tools:srcCompat="@drawable/ic_baseline_camera_front_24" /> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/takePhoto" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/take_photo" + android:tint="@android:color/white" + app:backgroundTint="@color/colorPrimary" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/toggle_torch" + app:layout_constraintStart_toEndOf="@id/switchCamera" + app:srcCompat="@drawable/ic_baseline_photo_camera_24" /> + + <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/colorPrimary" + app:fabSize="mini" + app:layout_constraintBottom_toBottomOf="@id/takePhoto" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/takePhoto" + app:layout_constraintTop_toTopOf="@id/takePhoto" + tools:srcCompat="@drawable/ic_baseline_flash_on_24" /> + </androidx.constraintlayout.widget.ConstraintLayout> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/dialog_attachment.xml b/app/src/main/res/layout/dialog_attachment.xml index 719393a94..dbc3332ba 100644 --- a/app/src/main/res/layout/dialog_attachment.xml +++ b/app/src/main/res/layout/dialog_attachment.xml @@ -105,6 +105,39 @@ </LinearLayout> + <LinearLayout + android:id="@+id/menu_attach_picture_from_cam" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/selectableItemBackground" + android:orientation="horizontal" + android:paddingLeft="@dimen/standard_padding" + android:paddingTop="@dimen/standard_half_padding" + android:paddingRight="@dimen/standard_padding" + android:paddingBottom="@dimen/standard_half_padding" + tools:ignore="UseCompoundDrawables"> + + <ImageView + android:id="@+id/menu_icon_attach_picture_from_cam" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@null" + android:src="@drawable/ic_baseline_photo_camera_24" + app:tint="@color/colorPrimary" /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/txt_attach_picture_from_cam" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="start|center_vertical" + android:layout_marginStart="@dimen/standard_margin" + android:text="@string/nc_upload_picture_from_cam" + android:textAlignment="viewStart" + android:textColor="@color/high_emphasis_text" + android:textSize="@dimen/bottom_sheet_text_size" /> + + </LinearLayout> + <LinearLayout android:id="@+id/menu_attach_file_from_cloud" android:layout_width="match_parent" diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d8422386c..d57a3bf7f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -89,4 +89,7 @@ <!-- voicemessage --> <color name="nc_voice_message_outgoing_controls">#606060</color> + <!-- camera --> + <color name="transparent_black">#7f000000</color> + </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff1ce3b6b..0f0270a78 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -389,6 +389,7 @@ <!-- Upload --> <string name="nc_add_file">Add to conversation</string> <string name="nc_upload_local_file">Upload local file</string> + <string name="nc_upload_picture_from_cam">Upload from camera</string> <string name="nc_upload_from_cloud">Share from %1$s</string> <string name="nc_upload_failed">Sorry, upload failed</string> <string name="nc_upload_choose_local_files">Choose files</string> @@ -473,4 +474,7 @@ <string name="nc_dialog_invalid_password">Invalid password</string> <string name="nc_dialog_reauth_or_delete">Do you want to reauthorize or delete this account?</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 ebd6e739b..d64963a9e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -55,6 +55,18 @@ <item name="elevation">1dp</item> </style> + <style name="TransparentTheme" parent="Theme.AppCompat.NoActionBar"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowBackground">@android:color/background_dark</item> + <item name="android:colorBackgroundCacheHint">@null</item> + <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="ErrorAppearance" parent="@android:style/TextAppearance"> <item name="android:textColor">@color/nc_darkRed</item> <item name="android:textSize">12sp</item>