Browse Source

Merge pull request #1542 from nextcloud/feature/306/directCamUpload

Adding upload from camera action
Andy Scherzinger 3 years ago
parent
commit
39d0052fb4
54 changed files with 1602 additions and 130 deletions
  1. 6 0
      app/build.gradle
  2. 5 0
      app/src/main/AndroidManifest.xml
  3. 409 0
      app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java
  4. 1 1
      app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java
  5. 68 0
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
  6. 0 3
      app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java
  7. 1 1
      app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.java
  8. 1 1
      app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java
  9. 64 36
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java
  10. 117 0
      app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java
  11. 17 30
      app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
  12. 1 1
      app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java
  13. 13 8
      app/src/main/java/com/nextcloud/talk/ui/dialog/ScopeDialog.kt
  14. 1 0
      app/src/main/java/com/nextcloud/talk/utils/BitmapShrinker.kt
  15. 2 5
      app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySize.java
  16. 68 0
      app/src/main/java/com/nextcloud/talk/utils/FileUtils.java
  17. 3 5
      app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java
  18. 1 2
      app/src/main/res/drawable-v24/ic_launcher_background.xml
  19. 9 10
      app/src/main/res/drawable/current_location_circle.xml
  20. 16 0
      app/src/main/res/drawable/ic_baseline_attach_file_24.xml
  21. 16 0
      app/src/main/res/drawable/ic_baseline_attachment_24.xml
  22. 21 0
      app/src/main/res/drawable/ic_baseline_flash_off_24.xml
  23. 21 0
      app/src/main/res/drawable/ic_baseline_flash_on_24.xml
  24. 32 0
      app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml
  25. 16 0
      app/src/main/res/drawable/ic_baseline_gps_fixed_24.xml
  26. 16 0
      app/src/main/res/drawable/ic_baseline_keyboard_24.xml
  27. 16 0
      app/src/main/res/drawable/ic_baseline_location_on_24.xml
  28. 16 0
      app/src/main/res/drawable/ic_baseline_location_on_red_24.xml
  29. 16 0
      app/src/main/res/drawable/ic_baseline_mic_24.xml
  30. 16 0
      app/src/main/res/drawable/ic_baseline_mic_red_24.xml
  31. 16 0
      app/src/main/res/drawable/ic_baseline_pause_voice_message_24.xml
  32. 22 0
      app/src/main/res/drawable/ic_baseline_photo_camera_24.xml
  33. 16 0
      app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml
  34. 16 0
      app/src/main/res/drawable/ic_baseline_play_arrow_voice_message_24.xml
  35. 16 0
      app/src/main/res/drawable/ic_content_copy.xml
  36. 29 0
      app/src/main/res/drawable/ic_crop_16_9.xml
  37. 29 0
      app/src/main/res/drawable/ic_crop_4_3.xml
  38. 26 0
      app/src/main/res/drawable/ic_high_quality.xml
  39. 29 0
      app/src/main/res/drawable/ic_low_quality.xml
  40. 16 0
      app/src/main/res/drawable/ic_menu.xml
  41. 23 0
      app/src/main/res/drawable/ic_refresh.xml
  42. 26 4
      app/src/main/res/drawable/ic_reply.xml
  43. 4 2
      app/src/main/res/drawable/ic_search_grey.xml
  44. 20 19
      app/src/main/res/drawable/ic_settings.xml
  45. 236 0
      app/src/main/res/layout/activity_take_picture.xml
  46. 33 0
      app/src/main/res/layout/dialog_attachment.xml
  47. 2 0
      app/src/main/res/values/colors.xml
  48. 10 0
      app/src/main/res/values/strings.xml
  49. 27 0
      app/src/main/res/values/styles.xml
  50. 5 0
      drawable_resources/other/ic_crop_16_9.svg
  51. 5 0
      drawable_resources/other/ic_crop_4_3.svg
  52. 5 0
      drawable_resources/other/ic_low_quality.svg
  53. 1 1
      scripts/analysis/findbugs-results.txt
  54. 1 1
      scripts/analysis/lint-results.txt

+ 6 - 0
app/build.gradle

@@ -187,6 +187,12 @@ dependencies {
     ktlint "com.pinterest:ktlint:0.43.0"
     implementation 'org.conscrypt:conscrypt-android:2.5.2'
 
+    implementation 'androidx.camera:camera-core:1.0.2'
+    implementation 'androidx.camera:camera-camera2:1.0.2'
+    implementation 'androidx.camera:camera-lifecycle:1.0.2'
+    implementation 'androidx.camera:camera-view:1.0.0-alpha28'
+    implementation "androidx.exifinterface:exifinterface:1.3.3"
+
     implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
 
     implementation 'androidx.biometric:biometric:1.1.0'

+ 5 - 0
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" />

+ 409 - 0
app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java

@@ -0,0 +1,409 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
+import android.view.OrientationEventListener;
+import android.view.ScaleGestureDetector;
+import android.view.Surface;
+import android.view.View;
+import android.widget.Toast;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.nextcloud.talk.R;
+import com.nextcloud.talk.databinding.ActivityTakePictureBinding;
+import com.nextcloud.talk.models.TakePictureViewModel;
+import com.nextcloud.talk.utils.BitmapShrinker;
+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.appcompat.content.res.AppCompatResources;
+import androidx.camera.core.AspectRatio;
+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.exifinterface.media.ExifInterface;
+import androidx.lifecycle.ViewModelProvider;
+
+public class TakePhotoActivity extends AppCompatActivity {
+
+    private static final String TAG = TakePhotoActivity.class.getSimpleName();
+
+    private static final float MAX_SCALE = 6.0f;
+    private static final float MEDIUM_SCALE = 2.45f;
+
+    private ActivityTakePictureBinding binding;
+    private TakePictureViewModel viewModel;
+
+    private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
+    private OrientationEventListener orientationEventListener;
+
+    private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss", Locale.ROOT);
+
+    private Camera camera;
+
+    @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();
+
+                camera = cameraProvider.bindToLifecycle(
+                    this,
+                    viewModel.getCameraSelector(),
+                    getImageCapture(
+                        viewModel.isCropEnabled().getValue(), viewModel.isLowResolutionEnabled().getValue()),
+                    getPreview(viewModel.isCropEnabled().getValue()));
+
+                viewModel.getTorchToggleButtonImageResource()
+                    .observe(
+                        this,
+                        res -> binding.toggleTorch.setIcon(ContextCompat.getDrawable(this, res)));
+                viewModel.isTorchEnabled()
+                    .observe(
+                        this,
+                        enabled -> camera.getCameraControl().enableTorch(viewModel.isTorchEnabled().getValue()));
+                binding.toggleTorch.setOnClickListener((v) -> viewModel.toggleTorchEnabled());
+
+                viewModel.getCropToggleButtonImageResource()
+                    .observe(
+                        this,
+                        res -> binding.toggleCrop.setIcon(ContextCompat.getDrawable(this, res)));
+                viewModel.isCropEnabled()
+                    .observe(
+                        this,
+                        enabled -> {
+                            cameraProvider.unbindAll();
+                            camera = cameraProvider.bindToLifecycle(
+                                this,
+                                viewModel.getCameraSelector(),
+                                getImageCapture(
+                                    viewModel.isCropEnabled().getValue(), viewModel.isLowResolutionEnabled().getValue()),
+                                getPreview(viewModel.isCropEnabled().getValue()));
+                            camera.getCameraControl().enableTorch(viewModel.isTorchEnabled().getValue());
+                        });
+                binding.toggleCrop.setOnClickListener((v) -> viewModel.toggleCropEnabled());
+
+                viewModel.getLowResolutionToggleButtonImageResource()
+                    .observe(
+                        this,
+                        res -> binding.toggleLowres.setIcon(ContextCompat.getDrawable(this, res)));
+                viewModel.isLowResolutionEnabled()
+                    .observe(
+                        this,
+                        enabled -> {
+                            cameraProvider.unbindAll();
+                            camera = cameraProvider.bindToLifecycle(
+                                this,
+                                viewModel.getCameraSelector(),
+                                getImageCapture(
+                                    viewModel.isCropEnabled().getValue(), viewModel.isLowResolutionEnabled().getValue()),
+                                getPreview(viewModel.isCropEnabled().getValue()));
+                            camera.getCameraControl().enableTorch(viewModel.isTorchEnabled().getValue());
+                        });
+                binding.toggleLowres.setOnClickListener((v) -> viewModel.toggleLowResolutionEnabled());
+
+                binding.switchCamera.setOnClickListener((v) -> {
+                    viewModel.toggleCameraSelector();
+                    cameraProvider.unbindAll();
+                    camera = cameraProvider.bindToLifecycle(
+                        this,
+                        viewModel.getCameraSelector(),
+                        getImageCapture(
+                            viewModel.isCropEnabled().getValue(), viewModel.isLowResolutionEnabled().getValue()),
+                        getPreview(viewModel.isCropEnabled().getValue()));
+                });
+                binding.retake.setOnClickListener((v) -> {
+                    Uri uri = (Uri) binding.photoPreview.getTag();
+                    File photoFile = new File(uri.getPath());
+                    if (!photoFile.delete()) {
+                        Log.w(TAG, "Error deleting temp camera image");
+                    }
+                    binding.takePhoto.setEnabled(true);
+                    binding.photoPreview.setTag(null);
+                    showCameraElements();
+                });
+                binding.send.setOnClickListener((v) -> {
+                    Uri uri = (Uri) binding.photoPreview.getTag();
+                    setResult(RESULT_OK, new Intent().setDataAndType(uri, "image/jpeg"));
+                    binding.photoPreview.setTag(null);
+                    finish();
+                });
+
+                ScaleGestureDetector mDetector =
+                    new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener(){
+                        @Override
+                        public boolean onScale(ScaleGestureDetector detector){
+                            float ratio = camera.getCameraInfo().getZoomState().getValue().getZoomRatio();
+                            float delta = detector.getScaleFactor();
+                            camera.getCameraControl().setZoomRatio(ratio * delta);
+                            return true;
+                        }
+                    });
+                binding.preview.setOnTouchListener((v, event) -> {
+                    v.performClick();
+                    mDetector.onTouchEvent(event);
+                    return true;
+                });
+
+                // Enable enlarging the image more than default 3x maximumScale.
+                // Medium scale adapted to make double-tap behaviour more consistent.
+                binding.photoPreview.setMaximumScale(MAX_SCALE);
+                binding.photoPreview.setMediumScale(MEDIUM_SCALE);
+            } 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));
+    }
+
+    @Override
+    public void onBackPressed() {
+        Uri uri = (Uri) binding.photoPreview.getTag();
+
+        if (uri != null) {
+            File photoFile = new File(uri.getPath());
+            if (!photoFile.delete()) {
+                Log.w(TAG, "Error deleting temp camera image");
+            }
+            binding.photoPreview.setTag(null);
+        }
+
+        super.onBackPressed();
+    }
+
+    private void showCameraElements() {
+        binding.send.setVisibility(View.GONE);
+        binding.retake.setVisibility(View.GONE);
+        binding.photoPreview.setVisibility(View.INVISIBLE);
+
+        binding.preview.setVisibility(View.VISIBLE);
+        binding.takePhoto.setVisibility(View.VISIBLE);
+        binding.switchCamera.setVisibility(View.VISIBLE);
+        binding.toggleTorch.setVisibility(View.VISIBLE);
+        binding.toggleCrop.setVisibility(View.VISIBLE);
+        binding.toggleLowres.setVisibility(View.VISIBLE);
+    }
+
+    private void showPictureProcessingElements() {
+        binding.preview.setVisibility(View.INVISIBLE);
+        binding.takePhoto.setVisibility(View.GONE);
+        binding.switchCamera.setVisibility(View.GONE);
+        binding.toggleTorch.setVisibility(View.GONE);
+        binding.toggleCrop.setVisibility(View.GONE);
+        binding.toggleLowres.setVisibility(View.GONE);
+
+        binding.send.setVisibility(View.VISIBLE);
+        binding.retake.setVisibility(View.VISIBLE);
+        binding.photoPreview.setVisibility(View.VISIBLE);
+    }
+
+    private ImageCapture getImageCapture(Boolean crop, Boolean lowres) {
+        final ImageCapture imageCapture;
+        if (lowres) imageCapture = new ImageCapture.Builder()
+            .setTargetResolution(new Size(crop ? 1080 : 1440, 1920)).build();
+        else imageCapture = new ImageCapture.Builder()
+            .setTargetAspectRatio(crop ? AspectRatio.RATIO_16_9 : AspectRatio.RATIO_4_3).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) {
+                            setPreviewImage(photoFile);
+                            showPictureProcessingElements();
+                        }
+
+                        @Override
+                        public void onError(@NonNull ImageCaptureException e) {
+                            Log.e(TAG, "Error", e);
+
+                            if (!photoFile.delete()) {
+                                Log.w(TAG, "Deleting picture failed");
+                            }
+                            binding.takePhoto.setEnabled(true);
+                        }
+                    });
+            } catch (Exception e) {
+                Toast.makeText(this, R.string.take_photo_error_deleting_picture, Toast.LENGTH_SHORT).show();
+            }
+        });
+
+        return imageCapture;
+    }
+
+    private void setPreviewImage(File photoFile) {
+        final Uri savedUri = Uri.fromFile(photoFile);
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        int doubleScreenWidth = displayMetrics.widthPixels * 2;
+        int doubleScreenHeight = displayMetrics.heightPixels * 2;
+
+        Bitmap bitmap = BitmapShrinker.shrinkBitmap(photoFile.getAbsolutePath(),
+                                                    doubleScreenWidth,
+                                                    doubleScreenHeight);
+
+        binding.photoPreview.setImageBitmap(bitmap);
+        binding.photoPreview.setTag(savedUri);
+        viewModel.disableTorchIfEnabled();
+    }
+
+    public int getImageOrientation(File imageFile) {
+        int rotate = 0;
+        try {
+            ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+            int orientation = exif.getAttributeInt(
+                ExifInterface.TAG_ORIENTATION,
+                ExifInterface.ORIENTATION_NORMAL);
+
+            switch (orientation) {
+                case ExifInterface.ORIENTATION_ROTATE_270:
+                    rotate = 270;
+                    break;
+                case ExifInterface.ORIENTATION_ROTATE_180:
+                    rotate = 180;
+                    break;
+                case ExifInterface.ORIENTATION_ROTATE_90:
+                    rotate = 90;
+                    break;
+                default:
+                    rotate = 0;
+                    break;
+            }
+
+            Log.i(TAG, "ImageOrientation - Exif orientation: " + orientation + " - " + "Rotate value: " + rotate);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calculation rotation value");
+        }
+        return rotate;
+    }
+
+    private Preview getPreview(Boolean crop) {
+        Preview preview = new Preview.Builder()
+            .setTargetAspectRatio(crop ? AspectRatio.RATIO_16_9 : AspectRatio.RATIO_4_3).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();
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        if (binding.photoPreview.getTag() != null) {
+            savedInstanceState.putString("Uri", ((Uri) binding.photoPreview.getTag()).getPath());
+        }
+
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+
+        String uri = savedInstanceState.getString("Uri", null);
+
+        if (uri != null) {
+            File photoFile = new File(uri);
+            setPreviewImage(photoFile);
+            showPictureProcessingElements();
+        }
+    }
+
+    public static Intent createIntent(@NonNull Context context) {
+        return new Intent(context, TakePhotoActivity.class).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+    }
+}

+ 1 - 1
app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java

@@ -281,7 +281,7 @@ public class AccountVerificationController extends BaseController {
 
     private void storeProfile(String displayName, String userId) {
         userUtils.createOrUpdateUser(username, token,
-                baseUrl, displayName, null, true,
+                baseUrl, displayName, null, Boolean.TRUE,
                 userId, null, null,
                 appPreferences.getTemporaryClientCertAlias(), null)
                 .subscribeOn(Schedulers.io())

+ 68 - 0
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
@@ -990,6 +991,17 @@ class ChatController(args: Bundle) :
         }
     }
 
+    private fun isCameraPermissionGranted(): Boolean {
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return PermissionChecker.checkSelfPermission(
+                context!!,
+                Manifest.permission.CAMERA
+            ) == PermissionChecker.PERMISSION_GRANTED
+        } else {
+            true
+        }
+    }
+
     private fun startAudioRecording(file: String) {
         binding.messageInputView.audioRecordDuration.base = SystemClock.elapsedRealtime()
         binding.messageInputView.audioRecordDuration.start()
@@ -1079,6 +1091,15 @@ class ChatController(args: Bundle) :
         )
     }
 
+    private fun requestCameraPermissions() {
+        requestPermissions(
+            arrayOf(
+                Manifest.permission.CAMERA
+            ),
+            REQUEST_CAMERA_PERMISSION
+        )
+    }
+
     private fun checkReadOnlyState() {
         if (currentConversation != null && isAlive()) {
             if (currentConversation?.shouldShowLobby(conversationUser) ?: false ||
@@ -1221,6 +1242,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)
+                }
+            }
         }
     }
 
@@ -1246,6 +1295,15 @@ class ChatController(args: Bundle) :
                     Toast.LENGTH_LONG
                 ).show()
             }
+        } else if (requestCode == REQUEST_CAMERA_PERMISSION) {
+            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                Log.d(TAG, "launch cam activity since permission for cam has been granted")
+                startActivityForResult(TakePhotoActivity.createIntent(context!!), REQUEST_CODE_PICK_CAMERA)
+            } else {
+                Toast
+                    .makeText(context, context?.getString(R.string.take_photo_permission), Toast.LENGTH_LONG)
+                    .show()
+            }
         }
     }
 
@@ -2542,6 +2600,14 @@ class ChatController(args: Bundle) :
         }
     }
 
+    fun sendPictureFromCamIntent() {
+        if (!isCameraPermissionGranted()) {
+            requestCameraPermissions()
+        } else {
+            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 +2622,8 @@ 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_CAMERA_PERMISSION = 223
+        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

+ 0 - 3
app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java

@@ -77,7 +77,6 @@ import com.nextcloud.talk.models.json.generic.GenericOverall;
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.DisplayUtils;
-import com.nextcloud.talk.utils.DoNotDisturbUtils;
 import com.nextcloud.talk.utils.LoggingUtils;
 import com.nextcloud.talk.utils.SecurityUtils;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
@@ -132,8 +131,6 @@ public class SettingsController extends BaseController {
     private static final int ID_REMOVE_ACCOUNT_WARNING_DIALOG = 0;
     @BindView(R.id.settings_screen)
     MaterialPreferenceScreen settingsScreen;
-    @BindView(R.id.settings_proxy_choice)
-    MaterialChoicePreference proxyChoice;
     @BindView(R.id.settings_proxy_port_edit)
     MaterialEditTextPreference proxyPortEditText;
     @BindView(R.id.settings_licence)

+ 1 - 1
app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.java

@@ -96,7 +96,7 @@ public class SwitchAccountController extends BaseController {
                 UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity();
                 userUtils.createOrUpdateUser(null,
                         null, null, null,
-                        null, true, null, userEntity.getId(), null, null, null)
+                        null, Boolean.TRUE, null, userEntity.getId(), null, null, null)
                         .subscribe(new Observer<UserEntity>() {
                             @Override
                             public void onSubscribe(Disposable d) {

+ 1 - 1
app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java

@@ -398,7 +398,7 @@ public class WebViewLoginController extends BaseController {
                 if (isPasswordUpdate) {
                     if (currentUser != null) {
                         userQueryDisposable = userUtils.createOrUpdateUser(null, loginData.getToken(),
-                                null, null, "", true,
+                                null, null, "", Boolean.TRUE,
                                 null, currentUser.getId(), null, appPreferences.getTemporaryClientCertAlias(), null)
                                 .subscribeOn(Schedulers.io())
                                 .observeOn(AndroidSchedulers.mainThread())

+ 64 - 36
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java

@@ -21,21 +21,14 @@
 package com.nextcloud.talk.controllers.bottomsheet;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.work.Data;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkManager;
-import autodagger.AutoInjector;
-import butterknife.BindView;
+
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
 import com.kennyc.bottomsheet.adapters.AppAdapter;
@@ -55,17 +48,30 @@ import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.ShareUtils;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
-import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
+
 import org.greenrobot.eventbus.EventBus;
 import org.parceler.Parcel;
 import org.parceler.Parcels;
 
-import javax.inject.Inject;
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.work.Data;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+import autodagger.AutoInjector;
+import butterknife.BindView;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
+
 @AutoInjector(NextcloudTalkApplication.class)
 public class CallMenuController extends BaseController implements FlexibleAdapter.OnItemClickListener {
     @BindView(R.id.recycler_view)
@@ -77,6 +83,9 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
     @Inject
     UserUtils userUtils;
 
+    @Inject
+    Context context;
+
     private Conversation conversation;
     private List<AbstractFlexibleItem> menuItems;
     private FlexibleAdapter<AbstractFlexibleItem> adapter;
@@ -104,6 +113,7 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
     }
 
     @Override
+    @NonNull
     protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
         return inflater.inflate(R.layout.controller_call_menu, container, false);
     }
@@ -153,45 +163,57 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
             if (conversation.isFavorite()) {
                 menuItems.add(new MenuItem(getResources().getString(R.string.nc_remove_from_favorites), 97, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_border_black_24dp, R.color.grey_600)));
             } else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "favorites")) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_add_to_favorites)
-                        , 98, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_black_24dp, R.color.grey_600)));
+                menuItems.add(new MenuItem(getResources().getString(R.string.nc_add_to_favorites),
+                                           98,
+                                           DisplayUtils.getTintedDrawable(getResources(),
+                                                                          R.drawable.ic_star_black_24dp,
+                                                                          R.color.grey_600)));
             }
 
             if (conversation.isNameEditable(currentUser)) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename), 2, getResources().getDrawable(R.drawable
-                        .ic_pencil_grey600_24dp)));
+                menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename),
+                                           2,
+                                           ContextCompat.getDrawable(context,
+                                                                     R.drawable.ic_pencil_grey600_24dp)));
             }
 
             if (conversation.canModerate(currentUser)) {
                 if (!conversation.isPublic()) {
-                    menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_public), 3, getResources().getDrawable(R.drawable
-                            .ic_link_grey600_24px)));
+                    menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_public),
+                                               3, ContextCompat.getDrawable(context,
+                                                                            R.drawable.ic_link_grey600_24px)));
                 } else {
                     if (conversation.isHasPassword()) {
-                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_change_password), 4, getResources().getDrawable(R.drawable
-                                .ic_lock_grey600_24px)));
-                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_clear_password), 5, getResources().getDrawable(R.drawable
-                                .ic_lock_open_grey600_24dp)));
+                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_change_password),
+                                                   4, ContextCompat.getDrawable(context,
+                                                                                R.drawable.ic_lock_grey600_24px)));
+                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_clear_password),
+                                                   5,
+                                                   ContextCompat.getDrawable(context,
+                                                                             R.drawable.ic_lock_open_grey600_24dp)));
                     } else {
-                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_set_password), 6, getResources().getDrawable(R.drawable
-                                .ic_lock_plus_grey600_24dp)));
+                        menuItems.add(new MenuItem(getResources().getString(R.string.nc_set_password),
+                                                   6, ContextCompat.getDrawable(context,
+                                                                                R.drawable.ic_lock_plus_grey600_24dp)));
                     }
                 }
 
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_delete_call), 9, getResources().getDrawable(R.drawable
-                        .ic_delete_grey600_24dp)));
+                menuItems.add(new MenuItem(getResources().getString(R.string.nc_delete_call),
+                                           9, ContextCompat.getDrawable(context,
+                                                                        R.drawable.ic_delete_grey600_24dp)));
             }
 
             if (conversation.isPublic()) {
-                menuItems.add(new MenuItem(getResources().getString(R.string.nc_share_link), 7, getResources().getDrawable(R.drawable
-                        .ic_link_grey600_24px)));
+                menuItems.add(new MenuItem(getResources().getString(R.string.nc_share_link),
+                                           7, ContextCompat.getDrawable(context,
+                                                                        R.drawable.ic_link_grey600_24px)));
                 if (conversation.canModerate(currentUser)) {
-                    menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_private), 8, getResources().getDrawable(R.drawable
-                            .ic_group_grey600_24px)));
+                    menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_private),
+                                               8, ContextCompat.getDrawable(context,
+                                                                            R.drawable.ic_group_grey600_24px)));
                 }
             }
 
-
             if (conversation.canLeave(currentUser)) {
                 menuItems.add(new MenuItem(getResources().getString(R.string.nc_leave), 1,
                         DisplayUtils.getTintedDrawable(getResources(),
@@ -202,8 +224,10 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
             prepareIntent();
             List<AppAdapter.AppInfo> appInfoList = ShareUtils.getShareApps(getActivity(), shareIntent, null,
                     null);
-            menuItems.add(new AppItem(getResources().getString(R.string.nc_share_link_via), "", "",
-                    getResources().getDrawable(R.drawable.ic_link_grey600_24px)));
+            menuItems.add(new AppItem(getResources().getString(R.string.nc_share_link_via),
+                                      "",
+                                      "",
+                                      ContextCompat.getDrawable(context, R.drawable.ic_link_grey600_24px)));
             if (appInfoList != null) {
                 for (AppAdapter.AppInfo appInfo : appInfoList) {
                     menuItems.add(new AppItem(appInfo.title, appInfo.packageName, appInfo.name, appInfo.drawable));
@@ -211,8 +235,12 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
             }
         } else {
             menuItems.add(new MenuItem(getResources().getString(R.string.nc_start_conversation), 0, null));
-            menuItems.add(new MenuItem(getResources().getString(R.string.nc_new_conversation), 1, getResources().getDrawable(R.drawable.ic_add_grey600_24px)));
-            menuItems.add(new MenuItem(getResources().getString(R.string.nc_join_via_link), 2, getResources().getDrawable(R.drawable.ic_link_grey600_24px)));
+            menuItems.add(new MenuItem(getResources().getString(R.string.nc_new_conversation),
+                                       1, ContextCompat.getDrawable(context,
+                                                                    R.drawable.ic_add_grey600_24px)));
+            menuItems.add(new MenuItem(getResources().getString(R.string.nc_join_via_link),
+                                       2, ContextCompat.getDrawable(context,
+                                                                    R.drawable.ic_link_grey600_24px)));
         }
     }
 

+ 117 - 0
app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java

@@ -0,0 +1,117 @@
+/*
+ * 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<Boolean> torchEnabled = new MutableLiveData<>(Boolean.FALSE);
+
+    @NonNull
+    private final MutableLiveData<Boolean> lowResolutionEnabled = new MutableLiveData<>(Boolean.FALSE);
+
+    @NonNull
+    private final MutableLiveData<Boolean> cropEnabled = new MutableLiveData<>(Boolean.FALSE);
+
+    @NonNull
+    public CameraSelector getCameraSelector() {
+        return this.cameraSelector;
+    }
+
+    public void toggleCameraSelector() {
+        if (this.cameraSelector == DEFAULT_BACK_CAMERA) {
+            this.cameraSelector = DEFAULT_FRONT_CAMERA;
+            if (this.torchEnabled.getValue()) {
+                toggleTorchEnabled();
+            }
+        } else {
+            this.cameraSelector = DEFAULT_BACK_CAMERA;
+        }
+    }
+
+    public void disableTorchIfEnabled() {
+        if (this.torchEnabled.getValue()) {
+            toggleTorchEnabled();
+        }
+    }
+
+    public void toggleTorchEnabled() {
+        //noinspection ConstantConditions
+        this.torchEnabled.postValue(!this.torchEnabled.getValue());
+    }
+
+    public void toggleLowResolutionEnabled() {
+        //noinspection ConstantConditions
+        this.lowResolutionEnabled.postValue(!this.lowResolutionEnabled.getValue());
+    }
+
+    public void toggleCropEnabled() {
+        //noinspection ConstantConditions
+        this.cropEnabled.postValue(!this.cropEnabled.getValue());
+    }
+
+    public LiveData<Boolean> isTorchEnabled() {
+        return this.torchEnabled;
+    }
+
+    public LiveData<Boolean> isLowResolutionEnabled() {
+        return this.lowResolutionEnabled;
+    }
+
+    public LiveData<Boolean> isCropEnabled() {
+        return this.cropEnabled;
+    }
+
+    public LiveData<Integer> getTorchToggleButtonImageResource() {
+        return Transformations.map(isTorchEnabled(), enabled -> enabled
+            ? R.drawable.ic_baseline_flash_off_24
+            : R.drawable.ic_baseline_flash_on_24);
+    }
+
+    public LiveData<Integer> getLowResolutionToggleButtonImageResource() {
+        return Transformations.map(isLowResolutionEnabled(), enabled -> enabled
+            ? R.drawable.ic_high_quality
+            : R.drawable.ic_low_quality);
+    }
+
+    public LiveData<Integer> getCropToggleButtonImageResource() {
+        return Transformations.map(isCropEnabled(), enabled -> enabled
+            ? R.drawable.ic_crop_4_3
+            : R.drawable.ic_crop_16_9);
+    }
+}

+ 17 - 30
app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt

@@ -2,7 +2,9 @@
  * Nextcloud Talk application
  *
  * @author Marcel Hibbe
+ * @author Andy Scherzinger
  * Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
+ * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
  *
  * 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
@@ -24,47 +26,26 @@ import android.app.Activity
 import android.os.Bundle
 import android.view.View
 import android.view.ViewGroup
-import android.widget.LinearLayout
-import androidx.appcompat.widget.AppCompatTextView
-import butterknife.BindView
-import butterknife.ButterKnife
-import butterknife.Unbinder
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetDialog
 import com.nextcloud.talk.R
 import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
 import com.nextcloud.talk.controllers.ChatController
+import com.nextcloud.talk.databinding.DialogAttachmentBinding
 import com.nextcloud.talk.models.database.CapabilitiesUtil
 
 class AttachmentDialog(val activity: Activity, var chatController: ChatController) : BottomSheetDialog(activity) {
 
-    @BindView(R.id.menu_share_location)
-    @JvmField
-    var shareLocationItem: LinearLayout? = null
-
-    @BindView(R.id.txt_share_location)
-    @JvmField
-    var shareLocation: AppCompatTextView? = null
-
-    @BindView(R.id.txt_attach_file_from_local)
-    @JvmField
-    var attachFromLocal: AppCompatTextView? = null
-
-    @BindView(R.id.txt_attach_file_from_cloud)
-    @JvmField
-    var attachFromCloud: AppCompatTextView? = null
-
-    private var unbinder: Unbinder? = null
+    private lateinit var dialogAttachmentBinding: DialogAttachmentBinding
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        val view = layoutInflater.inflate(R.layout.dialog_attachment, null)
-        setContentView(view)
+        dialogAttachmentBinding = DialogAttachmentBinding.inflate(layoutInflater)
+        setContentView(dialogAttachmentBinding.root)
         window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-        unbinder = ButterKnife.bind(this, view)
 
         var serverName = CapabilitiesUtil.getServerName(chatController.conversationUser)
-        attachFromCloud?.text = chatController.resources?.let {
+        dialogAttachmentBinding.txtAttachFileFromCloud.text = chatController.resources?.let {
             if (serverName.isNullOrEmpty()) {
                 serverName = it.getString(R.string.nc_server_product_name)
             }
@@ -76,19 +57,25 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
                 "geo-location-sharing"
             )
         ) {
-            shareLocationItem?.visibility = View.GONE
+            dialogAttachmentBinding.menuShareLocation.visibility = View.GONE
         }
 
-        shareLocation?.setOnClickListener {
+        dialogAttachmentBinding.menuShareLocation.setOnClickListener {
             chatController.showShareLocationScreen()
             dismiss()
         }
 
-        attachFromLocal?.setOnClickListener {
+        dialogAttachmentBinding.menuAttachFileFromLocal.setOnClickListener {
             chatController.sendSelectLocalFileIntent()
             dismiss()
         }
-        attachFromCloud?.setOnClickListener {
+
+        dialogAttachmentBinding.menuAttachPictureFromCam.setOnClickListener {
+            chatController.sendPictureFromCamIntent()
+            dismiss()
+        }
+
+        dialogAttachmentBinding.menuAttachFileFromCloud.setOnClickListener {
             chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER)
             dismiss()
         }

+ 1 - 1
app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java

@@ -206,7 +206,7 @@ public class ChooseAccountDialogFragment extends DialogFragment {
                                              null,
                                              null,
                                              null,
-                                             true,
+                                             Boolean.TRUE,
                                              null, userEntity.getId(),
                                              null,
                                              null,

+ 13 - 8
app/src/main/java/com/nextcloud/talk/ui/dialog/ScopeDialog.kt

@@ -2,7 +2,9 @@
  * Nextcloud Talk application
  *
  * @author Tobias Kaminsky
+ * @author Andy Scherzinger
  * Copyright (C) 2021 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
+ * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
  *
  * 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
@@ -24,11 +26,11 @@ import android.content.Context
 import android.os.Bundle
 import android.view.View
 import android.view.ViewGroup
-import android.widget.LinearLayout
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetDialog
 import com.nextcloud.talk.R
 import com.nextcloud.talk.controllers.ProfileController
+import com.nextcloud.talk.databinding.DialogScopeBinding
 import com.nextcloud.talk.models.json.userprofile.Scope
 
 class ScopeDialog(
@@ -38,33 +40,36 @@ class ScopeDialog(
     private val position: Int
 ) :
     BottomSheetDialog(con) {
+
+    private lateinit var dialogScopeBinding: DialogScopeBinding
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        val view = layoutInflater.inflate(R.layout.dialog_scope, null)
-        setContentView(view)
+        dialogScopeBinding = DialogScopeBinding.inflate(layoutInflater)
+        setContentView(dialogScopeBinding.root)
 
         window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
 
         if (field == ProfileController.Field.DISPLAYNAME || field == ProfileController.Field.EMAIL) {
-            findViewById<LinearLayout>(R.id.scope_private)?.visibility = View.GONE
+            dialogScopeBinding.scopePrivate.visibility = View.GONE
         }
 
-        findViewById<LinearLayout>(R.id.scope_private)?.setOnClickListener {
+        dialogScopeBinding.scopePrivate.setOnClickListener {
             userInfoAdapter.updateScope(position, Scope.PRIVATE)
             dismiss()
         }
 
-        findViewById<LinearLayout>(R.id.scope_local)?.setOnClickListener {
+        dialogScopeBinding.scopeLocal.setOnClickListener {
             userInfoAdapter.updateScope(position, Scope.LOCAL)
             dismiss()
         }
 
-        findViewById<LinearLayout>(R.id.scope_federated)?.setOnClickListener {
+        dialogScopeBinding.scopeFederated.setOnClickListener {
             userInfoAdapter.updateScope(position, Scope.FEDERATED)
             dismiss()
         }
 
-        findViewById<LinearLayout>(R.id.scope_published)?.setOnClickListener {
+        dialogScopeBinding.scopePublished.setOnClickListener {
             userInfoAdapter.updateScope(position, Scope.PUBLISHED)
             dismiss()
         }

+ 1 - 0
app/src/main/java/com/nextcloud/talk/utils/BitmapShrinker.kt

@@ -34,6 +34,7 @@ object BitmapShrinker {
     private const val DEGREES_180 = 180f
     private const val DEGREES_270 = 270f
 
+    @JvmStatic
     fun shrinkBitmap(
         path: String,
         reqWidth: Int,

+ 2 - 5
app/src/main/java/com/nextcloud/talk/utils/FileSortOrderBySize.java

@@ -46,16 +46,13 @@ public class FileSortOrderBySize extends FileSortOrder {
 
         Collections.sort(files, (o1, o2) -> {
             if (!o1.getModel().isFile() && !o2.getModel().isFile()) {
-                Long obj1 = o1.getModel().size;
-                return multiplier * obj1.compareTo(o2.getModel().getSize());
+                return multiplier * Long.compare(o1.getModel().getSize(), o2.getModel().getSize());
             } else if (!o1.getModel().isFile()) {
                 return -1;
-
             } else if (!o2.getModel().isFile()) {
                 return 1;
             } else {
-                Long obj1 = o1.getModel().getSize();
-                return multiplier * obj1.compareTo(o2.getModel().getSize());
+                return multiplier * Long.compare(o1.getModel().getSize(), o2.getModel().getSize());
             }
         });
 

+ 68 - 0
app/src/main/java/com/nextcloud/talk/utils/FileUtils.java

@@ -0,0 +1,68 @@
+/*
+ * 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.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;
+    }
+}

+ 3 - 5
app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java

@@ -60,7 +60,7 @@ import autodagger.AutoInjector;
 
 @AutoInjector(NextcloudTalkApplication.class)
 public class MagicPeerConnectionWrapper {
-    private static String TAG = "MagicPeerConnectionWrapper";
+    private static final String TAG = "MagicPeerConWrapper";
 
     private List<IceCandidate> iceCandidates = new ArrayList<>();
     private PeerConnection peerConnection;
@@ -129,7 +129,6 @@ public class MagicPeerConnectionWrapper {
                     EventBus.getDefault().post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap));
                 } else if (!hasMCU && hasInitiated) {
                     peerConnection.createOffer(magicSdpObserver, sdpConstraints);
-
                 }
             }
         }
@@ -178,7 +177,6 @@ public class MagicPeerConnectionWrapper {
         }
     }
 
-
     public void sendNickChannelData(DataChannelMessageNick dataChannelMessage) {
         ByteBuffer buffer;
         if (magicDataChannel != null) {
@@ -186,7 +184,7 @@ public class MagicPeerConnectionWrapper {
                 buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes());
                 magicDataChannel.send(new DataChannel.Buffer(buffer, false));
             } catch (IOException e) {
-                Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage.toString());
+                Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage);
             }
         }
     }
@@ -198,7 +196,7 @@ public class MagicPeerConnectionWrapper {
                 buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes());
                 magicDataChannel.send(new DataChannel.Buffer(buffer, false));
             } catch (IOException e) {
-                Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage.toString());
+                Log.d(TAG, "Failed to send channel data, attempting regular " + dataChannelMessage);
             }
         }
     }

+ 1 - 2
app/src/main/res/drawable-v24/ic_launcher_background.xml

@@ -18,8 +18,7 @@
   ~  * You should have received a copy of the GNU General Public License
   ~  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   ~  */
-  -->
-
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:aapt="http://schemas.android.com/aapt"
     android:width="108dp"

+ 9 - 10
app/src/main/res/drawable/current_location_circle.xml

@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-
-    <selector xmlns:android="http://schemas.android.com/apk/res/android">
-        <item>
-            <shape android:shape="oval">
-                <solid android:color="#0082C9"/>
-                <stroke android:color="#FFFFFF" android:width="2dp"/>
-                <size android:width="15dp" android:height="15dp"/>
-            </shape>
-        </item>
-    </selector>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="#0082C9" />
+            <stroke android:width="2dp" android:color="#FFFFFF" />
+            <size android:width="15dp" android:height="15dp" />
+        </shape>
+    </item>
+</selector>

+ 16 - 0
app/src/main/res/drawable/ic_baseline_attach_file_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_attachment_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 21 - 0
app/src/main/res/drawable/ic_baseline_flash_off_24.xml

@@ -0,0 +1,21 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<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>

+ 21 - 0
app/src/main/res/drawable/ic_baseline_flash_on_24.xml

@@ -0,0 +1,21 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<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>

+ 32 - 0
app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml

@@ -0,0 +1,32 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M9,12c0,1.66 1.34,3 3,3s3,-1.34 3,-3s-1.34,-3 -3,-3S9,10.34 9,12z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M8,10V8H5.09C6.47,5.61 9.05,4 12,4c3.72,0 6.85,2.56 7.74,6h2.06c-0.93,-4.56 -4.96,-8 -9.8,-8C8.73,2 5.82,3.58 4,6.01V4H2v6H8z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M16,14v2h2.91c-1.38,2.39 -3.96,4 -6.91,4c-3.72,0 -6.85,-2.56 -7.74,-6H2.2c0.93,4.56 4.96,8 9.8,8c3.27,0 6.18,-1.58 8,-4.01V20h2v-6H16z"/>
+</vector>

+ 16 - 0
app/src/main/res/drawable/ic_baseline_gps_fixed_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_keyboard_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_location_on_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_location_on_red_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_mic_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_mic_red_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_pause_voice_message_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 22 - 0
app/src/main/res/drawable/ic_baseline_photo_camera_24.xml

@@ -0,0 +1,22 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<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>

+ 16 - 0
app/src/main/res/drawable/ic_baseline_picture_in_picture_alt_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_baseline_play_arrow_voice_message_24.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"

+ 16 - 0
app/src/main/res/drawable/ic_content_copy.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector android:autoMirrored="true" android:height="24dp"
     android:tint="@color/medium_emphasis_text" android:viewportHeight="24.0"
     android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">

+ 29 - 0
app/src/main/res/drawable/ic_crop_16_9.xml

@@ -0,0 +1,29 @@
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Andy Scherzinger
+  ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
+  ~
+  ~ 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/>.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="nonZero"
+        android:pathData="M21,16L21,17C21,18.1 20.1,19 19,19L5,19C3.9,19 3,18.1 3,17L3,16L5,16L5,17L19,17L19,16L21,16ZM3,8L3,7C3,5.9 3.9,5 5,5L19,5C20.1,5 21,5.9 21,7L21,8L19,8L19,7L5,7L5,8L3,8ZM21,14.014C21,14.564 20.55,15.014 20,15.014L16.5,15.014L16.5,13.514L19.5,13.514L19.5,12.514L17.5,12.514C16.95,12.514 16.5,12.064 16.5,11.514L16.5,10.014C16.5,9.464 16.95,9.014 17.5,9.014L20,9.014C20.55,9.014 21,9.464 21,10.014L21,14.014ZM6,15L4.5,15L4.5,10.5L3,10.5L3,9L6,9L6,15ZM8,15C7.45,15 7,14.55 7,14L7,10C7,9.45 7.45,9 8,9L11.5,9L11.5,10.5L8.5,10.5L8.5,11.5L10.5,11.5C11.05,11.5 11.5,11.95 11.5,12.5L11.5,14C11.5,14.55 11.05,15 10.5,15L8,15ZM14.75,12.722L13.25,12.722L13.25,14.223L14.75,14.223L14.75,12.722ZM9.995,12.456L8.495,12.456L8.495,13.956L9.995,13.956L9.995,12.456ZM19.508,9.984L18.008,9.984L18.008,11.484L19.508,11.484L19.508,9.984ZM14.75,9.722L13.25,9.722L13.25,11.223L14.75,11.223L14.75,9.722Z" />
+</vector>

+ 29 - 0
app/src/main/res/drawable/ic_crop_4_3.xml

@@ -0,0 +1,29 @@
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Andy Scherzinger
+  ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
+  ~
+  ~ 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/>.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="nonZero"
+        android:pathData="M21,16L21,17C21,18.1 20.1,19 19,19L5,19C3.9,19 3,18.1 3,17L3,16L5,16L5,17L19,17L19,16L21,16ZM19,14L19,10C19,9.45 18.55,9 18,9L14.5,9L14.5,10.5L17.5,10.5L17.5,11.5L15.5,11.5L15.5,12.5L17.5,12.5L17.5,13.5L14.5,13.5L14.5,15L18,15C18.55,15 19,14.55 19,14ZM9.5,9L9.5,15L8,15L8,13.5L5,13.5L5,9L6.5,9L6.5,12L8,12L8,9L9.5,9ZM12.75,12.723L12.75,14.223L11.25,14.223L11.25,12.723L12.75,12.723ZM12.75,9.723L12.75,11.223L11.25,11.223L11.25,9.723L12.75,9.723ZM3,8L3,7C3,5.9 3.9,5 5,5L19,5C20.1,5 21,5.9 21,7L21,8L19,8L19,7L5,7L5,8L3,8Z" />
+</vector>

+ 26 - 0
app/src/main/res/drawable/ic_high_quality.xml

@@ -0,0 +1,26 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
+</vector>

+ 29 - 0
app/src/main/res/drawable/ic_low_quality.xml

@@ -0,0 +1,29 @@
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Andy Scherzinger
+  ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
+  ~
+  ~ 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/>.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M19,4L5,4C3.89,4 3,4.9 3,6L3,18C3,19.1 3.89,20 5,20L19,20C20.1,20 21,19.1 21,18L21,6C21,4.9 20.1,4 19,4ZM18,14C18,14.55 17.55,15 17,15L16.25,15L16.25,16.5L14.75,16.5L14.75,15L14,15C13.45,15 13,14.55 13,14L13,10C13,9.45 13.45,9 14,9L17,9C17.55,9 18,9.45 18,10L18,14ZM7.5,13.5L10.509,13.496L10.508,14.997L6,15L6,9L7.5,9L7.5,13.5ZM14.5,13.5L16.5,13.5L16.5,10.5L14.5,10.5L14.5,13.5Z"
+      android:fillType="nonZero"/>
+</vector>

+ 16 - 0
app/src/main/res/drawable/ic_menu.xml

@@ -1,3 +1,19 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="24dp"
         android:height="24dp"

+ 23 - 0
app/src/main/res/drawable/ic_refresh.xml

@@ -0,0 +1,23 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z" />
+</vector>

+ 26 - 4
app/src/main/res/drawable/ic_reply.xml

@@ -1,5 +1,27 @@
-<vector android:autoMirrored="true" android:height="24dp"
-    android:tint="@color/medium_emphasis_text" android:viewportHeight="24.0"
-    android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#FF000000" android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
+<!--
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="@color/medium_emphasis_text"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
 </vector>

+ 4 - 2
app/src/main/res/drawable/ic_search_grey.xml

@@ -15,9 +15,11 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
     android:width="24dp"
+    android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24">
-    <path android:fillColor="#757575" android:pathData="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
+    <path
+        android:fillColor="#757575"
+        android:pathData="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
 </vector>

+ 20 - 19
app/src/main/res/drawable/ic_settings.xml

@@ -1,25 +1,26 @@
 <!--
-  Nextcloud Talk application
+    @author Google LLC
+    Copyright (C) 2021 Google LLC
 
-  Copyright (C) 2020 Nextcloud.
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
 
-  This program is free software; you can redistribute it and/or
-  modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
-  License as published by the Free Software Foundation; either
-  version 3 of the License, or any later version.
+    http://www.apache.org/licenses/LICENSE-2.0
 
-  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 AFFERO GENERAL PUBLIC LICENSE for more details.
-
-  You should have received a copy of the GNU Affero General Public
-  License along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Icon provided by Android Material Library in Apache License 2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
 -->
-<vector android:height="24dp" android:tint="#666666"
-    android:viewportHeight="24" android:viewportWidth="24"
-    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#FF000000" android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#666666"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z" />
 </vector>

+ 236 - 0
app/src/main/res/layout/activity_take_picture.xml

@@ -0,0 +1,236 @@
+<?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:fitsSystemWindows="true"
+    tools:theme="@style/TransparentTheme">
+
+    <androidx.camera.view.PreviewView
+        android:id="@+id/preview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:scaleType="fitCenter" />
+
+    <com.github.chrisbanes.photoview.PhotoView
+        android:id="@+id/photo_preview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:contentDescription="@null"
+        android:scaleType="fitCenter"
+        android:visibility="invisible" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/toggle_crop"
+        style="@style/Widget.AppTheme.Button.IconButton"
+        android:layout_width="@dimen/min_size_clickable_area"
+        android:layout_height="@dimen/min_size_clickable_area"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true"
+        android:layout_marginStart="0dp"
+        android:layout_marginTop="12dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginBottom="12dp"
+        android:contentDescription="@string/take_photo_toggle_crop"
+        android:insetLeft="4dp"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:tint="@color/white"
+        app:backgroundTint="@color/camera_bg_tint"
+        app:cornerRadius="@dimen/button_corner_radius"
+        app:elevation="0dp"
+        app:icon="@drawable/ic_crop_16_9"
+        app:iconGravity="textStart"
+        app:iconPadding="0dp"
+        app:iconSize="24dp"
+        app:iconTint="@color/white"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/takePhoto"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/toggle_lowres"
+        style="@style/Widget.AppTheme.Button.IconButton"
+        android:layout_width="@dimen/min_size_clickable_area"
+        android:layout_height="@dimen/min_size_clickable_area"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true"
+        android:layout_marginStart="12dp"
+        android:layout_marginTop="12dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginBottom="12dp"
+        android:contentDescription="@string/take_photo_toggle_lowres"
+        android:insetLeft="4dp"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:padding="0dp"
+        android:tint="@android:color/white"
+        app:backgroundTint="@color/camera_bg_tint"
+        app:cornerRadius="@dimen/button_corner_radius"
+        app:elevation="0dp"
+        app:icon="@drawable/ic_low_quality"
+        app:iconGravity="textStart"
+        app:iconPadding="0dp"
+        app:iconSize="24dp"
+        app:iconTint="@color/white"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/takePhoto" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/toggle_torch"
+        style="@style/Widget.AppTheme.Button.IconButton"
+        android:layout_width="@dimen/min_size_clickable_area"
+        android:layout_height="@dimen/min_size_clickable_area"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true"
+        android:layout_marginStart="0dp"
+        android:layout_marginTop="12dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginBottom="12dp"
+        android:contentDescription="@string/take_photo_toggle_torch"
+        android:insetLeft="4dp"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:tint="@color/white"
+        app:backgroundTint="@color/camera_bg_tint"
+        app:cornerRadius="@dimen/button_corner_radius"
+        app:elevation="0dp"
+        app:icon="@drawable/ic_baseline_flash_on_24"
+        app:iconGravity="textStart"
+        app:iconPadding="0dp"
+        app:iconSize="24dp"
+        app:iconTint="@color/white"
+        app:layout_constraintBottom_toBottomOf="@id/takePhoto"
+        app:layout_constraintEnd_toStartOf="@id/takePhoto"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/switchCamera"
+        style="@style/Widget.AppTheme.Button.IconButton"
+        android:layout_width="@dimen/min_size_clickable_area"
+        android:layout_height="@dimen/min_size_clickable_area"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true"
+        android:layout_marginStart="12dp"
+        android:layout_marginTop="12dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginBottom="12dp"
+        android:contentDescription="@string/take_photo_switch_camera"
+        android:insetLeft="4dp"
+        android:insetTop="4dp"
+        android:insetRight="4dp"
+        android:insetBottom="4dp"
+        android:padding="0dp"
+        android:tint="@android:color/white"
+        app:backgroundTint="@color/camera_bg_tint"
+        app:cornerRadius="@dimen/button_corner_radius"
+        app:elevation="0dp"
+        app:icon="@drawable/ic_baseline_flip_camera_android_24"
+        app:iconGravity="textStart"
+        app:iconPadding="0dp"
+        app:iconSize="24dp"
+        app:iconTint="@color/white"
+        app:layout_constraintBottom_toBottomOf="@id/takePhoto"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/takePhoto" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/retake"
+        style="@style/Widget.AppTheme.Button.IconButton"
+        android:layout_width="@dimen/min_size_clickable_area"
+        android:layout_height="@dimen/min_size_clickable_area"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true"
+        android:layout_marginBottom="12dp"
+        android:contentDescription="@string/take_photo_retake_photo"
+        android:insetLeft="0dp"
+        android:insetTop="0dp"
+        android:insetRight="0dp"
+        android:insetBottom="0dp"
+        android:padding="0dp"
+        android:tint="@android:color/white"
+        android:visibility="gone"
+        app:backgroundTint="@color/camera_bg_tint"
+        app:cornerRadius="@dimen/button_corner_radius"
+        app:elevation="0dp"
+        app:icon="@drawable/ic_refresh"
+        app:iconGravity="textStart"
+        app:iconPadding="0dp"
+        app:iconSize="24dp"
+        app:iconTint="@color/white"
+        app:layout_constraintBottom_toBottomOf="@id/takePhoto"
+        app:layout_constraintEnd_toStartOf="@id/takePhoto"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/takePhoto"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="@dimen/standard_double_margin"
+        android:contentDescription="@string/take_photo"
+        android:tint="@android:color/white"
+        app:backgroundTint="@color/colorPrimary"
+        app:fabCustomSize="72dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:srcCompat="@drawable/ic_baseline_photo_camera_24" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true"
+        android:contentDescription="@string/take_photo_switch_camera"
+        android:paddingStart="24dp"
+        android:paddingTop="16dp"
+        android:paddingEnd="24dp"
+        android:paddingBottom="16dp"
+        android:text="@string/take_photo_send"
+        android:textAllCaps="false"
+        android:textColor="@color/white"
+        android:textSize="16sp"
+        android:theme="@style/Button.Primary"
+        android:tint="@android:color/white"
+        android:visibility="gone"
+        app:backgroundTint="@color/colorPrimary"
+        app:cornerRadius="48dp"
+        app:elevation="0dp"
+        app:icon="@drawable/ic_send"
+        app:iconGravity="textStart"
+        app:iconPadding="8dp"
+        app:iconSize="24dp"
+        app:iconTint="@color/white"
+        app:layout_constraintBottom_toBottomOf="@id/takePhoto"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/takePhoto" />
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 33 - 0
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"

+ 2 - 0
app/src/main/res/values/colors.xml

@@ -89,4 +89,6 @@
     <!-- voicemessage -->
     <color name="nc_voice_message_outgoing_controls">#606060</color>
 
+    <color name="camera_bg_tint">#99121212</color>
+
 </resources>

+ 10 - 0
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">Take photo</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,13 @@
     <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_retake_photo">Re-take photo</string>
+    <string name="take_photo_toggle_torch">Toggle torch</string>
+    <string name="take_photo_toggle_crop">Crop photo</string>
+    <string name="take_photo_toggle_lowres">Reduce image size</string>
+    <string name="take_photo_send">Send</string>
+    <string name="take_photo_error_deleting_picture">Error taking picture</string>
+    <string name="take_photo_permission">Taking a photo is not possible without permissions</string>
 </resources>

+ 27 - 0
app/src/main/res/values/styles.xml

@@ -55,6 +55,18 @@
         <item name="elevation">1dp</item>
     </style>
 
+    <style name="TransparentTheme" parent="Theme.MaterialComponents.NoActionBar.Bridge">
+        <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>
@@ -103,6 +115,21 @@
         <item name="searchHintIcon">@null</item>
     </style>
 
+    <style name="Button" parent="Widget.MaterialComponents.Button.UnelevatedButton">
+        <item name="colorButtonNormal">@color/colorPrimary</item>
+        <item name="android:textColor">@color/white</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:typeface">sans</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="Button.Primary" parent="Button">
+        <item name="colorButtonNormal">@color/colorPrimary</item>
+        <item name="android:textColor">@color/white</item>
+        <item name="android:typeface">sans</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
     <style name="Widget.AppTheme.Button.IconButton" parent="Widget.MaterialComponents.Button.TextButton">
         <item name="android:minWidth">0dp</item>
         <item name="android:insetLeft">0dp</item>

+ 5 - 0
drawable_resources/other/ic_crop_16_9.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M21,16L21,17C21,18.1 20.1,19 19,19L5,19C3.9,19 3,18.1 3,17L3,16L5,16L5,17L19,17L19,16L21,16ZM3,8L3,7C3,5.9 3.9,5 5,5L19,5C20.1,5 21,5.9 21,7L21,8L19,8L19,7L5,7L5,8L3,8ZM21,14.014C21,14.564 20.55,15.014 20,15.014L16.5,15.014L16.5,13.514L19.5,13.514L19.5,12.514L17.5,12.514C16.95,12.514 16.5,12.064 16.5,11.514L16.5,10.014C16.5,9.464 16.95,9.014 17.5,9.014L20,9.014C20.55,9.014 21,9.464 21,10.014L21,14.014ZM6,15L4.5,15L4.5,10.5L3,10.5L3,9L6,9L6,15ZM8,15C7.45,15 7,14.55 7,14L7,10C7,9.45 7.45,9 8,9L11.5,9L11.5,10.5L8.5,10.5L8.5,11.5L10.5,11.5C11.05,11.5 11.5,11.95 11.5,12.5L11.5,14C11.5,14.55 11.05,15 10.5,15L8,15ZM14.75,12.722L13.25,12.722L13.25,14.223L14.75,14.223L14.75,12.722ZM9.995,12.456L8.495,12.456L8.495,13.956L9.995,13.956L9.995,12.456ZM19.508,9.984L18.008,9.984L18.008,11.484L19.508,11.484L19.508,9.984ZM14.75,9.722L13.25,9.722L13.25,11.223L14.75,11.223L14.75,9.722Z" style="fill-rule:nonzero;"/>
+</svg>

+ 5 - 0
drawable_resources/other/ic_crop_4_3.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M21,16L21,17C21,18.1 20.1,19 19,19L5,19C3.9,19 3,18.1 3,17L3,16L5,16L5,17L19,17L19,16L21,16ZM19,14L19,10C19,9.45 18.55,9 18,9L14.5,9L14.5,10.5L17.5,10.5L17.5,11.5L15.5,11.5L15.5,12.5L17.5,12.5L17.5,13.5L14.5,13.5L14.5,15L18,15C18.55,15 19,14.55 19,14ZM9.5,9L9.5,15L8,15L8,13.5L5,13.5L5,9L6.5,9L6.5,12L8,12L8,9L9.5,9ZM12.75,12.723L12.75,14.223L11.25,14.223L11.25,12.723L12.75,12.723ZM12.75,9.723L12.75,11.223L11.25,11.223L11.25,9.723L12.75,9.723ZM3,8L3,7C3,5.9 3.9,5 5,5L19,5C20.1,5 21,5.9 21,7L21,8L19,8L19,7L5,7L5,8L3,8Z" style="fill-rule:nonzero;"/>
+</svg>

+ 5 - 0
drawable_resources/other/ic_low_quality.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M19,4L5,4C3.89,4 3,4.9 3,6L3,18C3,19.1 3.89,20 5,20L19,20C20.1,20 21,19.1 21,18L21,6C21,4.9 20.1,4 19,4ZM18,14C18,14.55 17.55,15 17,15L16.25,15L16.25,16.5L14.75,16.5L14.75,15L14,15C13.45,15 13,14.55 13,14L13,10C13,9.45 13.45,9 14,9L17,9C17.55,9 18,9.45 18,10L18,14ZM7.5,13.5L10.509,13.496L10.508,14.997L6,15L6,9L7.5,9L7.5,13.5ZM14.5,13.5L16.5,13.5L16.5,10.5L14.5,10.5L14.5,13.5Z" style="fill-rule:nonzero;"/>
+</svg>

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-568
+559

+ 1 - 1
scripts/analysis/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 1 error and 238 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 1 error and 222 warnings</span>