diff --git a/app/build.gradle b/app/build.gradle
index d81936839..f17cfde8b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -197,6 +197,10 @@ dependencies {
ktlint "com.pinterest:ktlint:0.42.1"
implementation 'org.conscrypt:conscrypt-android:2.5.2'
+ implementation 'androidx.camera:camera-camera2:1.0.1'
+ implementation 'androidx.camera:camera-lifecycle:1.0.1'
+ implementation 'androidx.camera:camera-view:1.0.0-alpha20'
+
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.biometric:biometric:1.0.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index acca95c7a..557db667a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -142,6 +142,11 @@
android:configChanges="orientation|keyboardHidden|screenSize">
+
+
diff --git a/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java b/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java
new file mode 100644
index 000000000..31798015f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/activities/TakePhotoActivity.java
@@ -0,0 +1,209 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Stefan Niedermann
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2021 Stefan Niedermann
+ *
+ * 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 .
+ */
+
+package com.nextcloud.talk.activities;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Size;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.widget.Toast;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.nextcloud.talk.R;
+import com.nextcloud.talk.databinding.ActivityTakePictureBinding;
+import com.nextcloud.talk.models.TakePictureViewModel;
+import com.nextcloud.talk.utils.FileUtils;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.camera.core.Camera;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProvider;
+
+public class TakePhotoActivity extends AppCompatActivity {
+
+ private static final String TAG = TakePhotoActivity.class.getSimpleName();
+
+ private ActivityTakePictureBinding binding;
+ private TakePictureViewModel viewModel;
+
+ private ListenableFuture cameraProviderFuture;
+ private OrientationEventListener orientationEventListener;
+
+ private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss", Locale.ROOT);
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ binding = ActivityTakePictureBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(TakePictureViewModel.class);
+
+ setContentView(binding.getRoot());
+
+ cameraProviderFuture = ProcessCameraProvider.getInstance(this);
+ cameraProviderFuture.addListener(() -> {
+ try {
+ final ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
+ final Preview preview = getPreview();
+ final ImageCapture imageCapture = getImageCapture();
+ final Camera camera = cameraProvider.bindToLifecycle(
+ this,
+ viewModel.getCameraSelector(),
+ imageCapture,
+ preview);
+
+ viewModel.getCameraSelectorToggleButtonImageResource()
+ .observe(
+ this,
+ res -> binding.switchCamera.setImageDrawable(ContextCompat.getDrawable(this, res)));
+ viewModel.getTorchToggleButtonImageResource()
+ .observe(
+ this,
+ res -> binding.toggleTorch.setImageDrawable(ContextCompat.getDrawable(this, res)));
+ viewModel.isTorchEnabled()
+ .observe(
+ this,
+ enabled -> camera.getCameraControl().enableTorch(enabled));
+
+ binding.toggleTorch.setOnClickListener((v) -> viewModel.toggleTorchEnabled());
+ binding.switchCamera.setOnClickListener((v) -> {
+ viewModel.toggleCameraSelector();
+ cameraProvider.unbindAll();
+ cameraProvider.bindToLifecycle(
+ this,
+ viewModel.getCameraSelector(),
+ imageCapture,
+ preview);
+ });
+ } catch (IllegalArgumentException | ExecutionException | InterruptedException e) {
+ Log.e(TAG, "Error taking picture", e);
+ Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }, ContextCompat.getMainExecutor(this));
+ }
+
+ private ImageCapture getImageCapture() {
+ final ImageCapture imageCapture = new ImageCapture.Builder().setTargetResolution(new Size(720, 1280)).build();
+
+ orientationEventListener = new OrientationEventListener(this) {
+ @Override
+ public void onOrientationChanged(int orientation) {
+ int rotation;
+
+ // Monitors orientation values to determine the target rotation value
+ if (orientation >= 45 && orientation < 135) {
+ rotation = Surface.ROTATION_270;
+ } else if (orientation >= 135 && orientation < 225) {
+ rotation = Surface.ROTATION_180;
+ } else if (orientation >= 225 && orientation < 315) {
+ rotation = Surface.ROTATION_90;
+ } else {
+ rotation = Surface.ROTATION_0;
+ }
+
+ imageCapture.setTargetRotation(rotation);
+ }
+ };
+ orientationEventListener.enable();
+
+ binding.takePhoto.setOnClickListener((v) -> {
+ binding.takePhoto.setEnabled(false);
+ final String photoFileName = dateFormat.format(new Date())+ ".jpg";
+ try {
+ final File photoFile = FileUtils.getTempCacheFile(this, "photos/" + photoFileName);
+ final ImageCapture.OutputFileOptions options =
+ new ImageCapture.OutputFileOptions.Builder(photoFile).build();
+ imageCapture.takePicture(
+ options,
+ ContextCompat.getMainExecutor(this),
+ new ImageCapture.OnImageSavedCallback() {
+
+ @Override
+ public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
+ final Uri savedUri = Uri.fromFile(photoFile);
+ Log.i(TAG, "onImageSaved - savedUri:" + savedUri);
+ setResult(RESULT_OK, new Intent().setDataAndType(savedUri, "image/jpeg"));
+ finish();
+ }
+
+ @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 Preview getPreview() {
+ Preview preview = new Preview.Builder().build();
+ preview.setSurfaceProvider(binding.preview.getSurfaceProvider());
+ return preview;
+ }
+
+ @Override
+ protected void onPause() {
+ if (this.orientationEventListener != null) {
+ this.orientationEventListener.disable();
+ }
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (this.orientationEventListener != null) {
+ this.orientationEventListener.enable();
+ }
+ }
+
+ public static Intent createIntent(@NonNull Context context) {
+ return new Intent(context, TakePhotoActivity.class).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
index dc80c46e4..294fd925c 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
+++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
@@ -96,6 +96,7 @@ import com.google.android.flexbox.FlexboxLayout
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MagicCallActivity
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
@@ -1208,6 +1209,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)
+ }
+ }
}
}
@@ -2535,6 +2564,10 @@ class ChatController(args: Bundle) :
}
}
+ fun sendPictureFromCamIntent() {
+ startActivityForResult(TakePhotoActivity.createIntent(context!!), REQUEST_CODE_PICK_CAMERA)
+ }
+
companion object {
private const val TAG = "ChatController"
private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
@@ -2549,6 +2582,7 @@ class ChatController(args: Bundle) :
private const val AGE_THREHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000)
private const val REQUEST_CODE_CHOOSE_FILE: Int = 555
private const val REQUEST_RECORD_AUDIO_PERMISSION = 222
+ private const val REQUEST_CODE_PICK_CAMERA: Int = 333
private const val OBJECT_MESSAGE: String = "{object}"
private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000
private const val VOICE_RECORD_CANCEL_SLIDER_X: Int = -50
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java
index a4f30050a..42b367b67 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java
+++ b/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 menuItems;
private FlexibleAdapter 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 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)));
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java b/app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java
new file mode 100644
index 000000000..eba301dd8
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/TakePictureViewModel.java
@@ -0,0 +1,80 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Stefan Niedermann
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2021 Stefan Niedermann
+ *
+ * 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 .
+ */
+
+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 cameraSelectorToggleButtonImageResource =
+ new MutableLiveData<>(R.drawable.ic_baseline_camera_front_24);
+ @NonNull
+ private final MutableLiveData torchEnabled = new MutableLiveData<>(false);
+
+ @NonNull
+ public CameraSelector getCameraSelector() {
+ return this.cameraSelector;
+ }
+
+ public LiveData getCameraSelectorToggleButtonImageResource() {
+ return this.cameraSelectorToggleButtonImageResource;
+ }
+
+ public void toggleCameraSelector() {
+ if (this.cameraSelector == DEFAULT_BACK_CAMERA) {
+ this.cameraSelector = DEFAULT_FRONT_CAMERA;
+ this.cameraSelectorToggleButtonImageResource.postValue(R.drawable.ic_baseline_camera_rear_24);
+ } else {
+ this.cameraSelector = DEFAULT_BACK_CAMERA;
+ this.cameraSelectorToggleButtonImageResource.postValue(R.drawable.ic_baseline_camera_front_24);
+ }
+ }
+
+ public void toggleTorchEnabled() {
+ //noinspection ConstantConditions
+ this.torchEnabled.postValue(!this.torchEnabled.getValue());
+ }
+
+ public LiveData isTorchEnabled() {
+ return this.torchEnabled;
+ }
+
+ public LiveData getTorchToggleButtonImageResource() {
+ return Transformations.map(isTorchEnabled(), enabled -> enabled
+ ? R.drawable.ic_baseline_flash_off_24
+ : R.drawable.ic_baseline_flash_on_24);
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
index 51b2959fb..ed6185bbe 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
@@ -54,6 +54,10 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
@JvmField
var attachFromCloud: AppCompatTextView? = null
+ @BindView(R.id.menu_attach_picture_from_cam)
+ @JvmField
+ var pictureFromCamItem: LinearLayout? = null
+
private var unbinder: Unbinder? = null
override fun onCreate(savedInstanceState: Bundle?) {
@@ -88,6 +92,12 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
chatController.sendSelectLocalFileIntent()
dismiss()
}
+
+ pictureFromCamItem?.setOnClickListener {
+ chatController.sendPictureFromCamIntent()
+ dismiss()
+ }
+
attachFromCloud?.setOnClickListener {
chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER)
dismiss()
diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileUtils.java b/app/src/main/java/com/nextcloud/talk/utils/FileUtils.java
new file mode 100644
index 000000000..e18e0147b
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/FileUtils.java
@@ -0,0 +1,46 @@
+package com.nextcloud.talk.utils;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import androidx.annotation.NonNull;
+
+public class FileUtils {
+ private static final String TAG = FileUtils.class.getSimpleName();
+
+ /**
+ * Creates a new {@link File}
+ */
+ public static File getTempCacheFile(@NonNull Context context, String fileName) throws IOException {
+ File cacheFile = new File(context.getApplicationContext().getFilesDir().getAbsolutePath() + "/" + fileName);
+
+ Log.v(TAG, "Full path for new cache file:" + cacheFile.getAbsolutePath());
+
+ final File tempDir = cacheFile.getParentFile();
+ if (tempDir == null) {
+ throw new FileNotFoundException("could not cacheFile.getParentFile()");
+ }
+ if (!tempDir.exists()) {
+ Log.v(TAG,
+ "The folder in which the new file should be created does not exist yet. Trying to create it…");
+ if (tempDir.mkdirs()) {
+ Log.v(TAG, "Creation successful");
+ } else {
+ throw new IOException("Directory for temporary file does not exist and could not be created.");
+ }
+ }
+
+ Log.v(TAG, "- Try to create actual cache file");
+ if (cacheFile.createNewFile()) {
+ Log.v(TAG, "Successfully created cache file");
+ } else {
+ throw new IOException("Failed to create cacheFile");
+ }
+
+ return cacheFile;
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java
index 705d47e22..4d29af22b 100644
--- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java
+++ b/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 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);
}
}
}
diff --git a/app/src/main/res/drawable/ic_baseline_camera_front_24.xml b/app/src/main/res/drawable/ic_baseline_camera_front_24.xml
new file mode 100644
index 000000000..25c1a79b8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_camera_front_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml b/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml
new file mode 100644
index 000000000..51cea2177
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_flash_off_24.xml b/app/src/main/res/drawable/ic_baseline_flash_off_24.xml
new file mode 100644
index 000000000..2a3b0ff5d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_flash_off_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_flash_on_24.xml b/app/src/main/res/drawable/ic_baseline_flash_on_24.xml
new file mode 100644
index 000000000..4574d0e20
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_flash_on_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml b/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml
new file mode 100644
index 000000000..497db8383
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_take_picture.xml b/app/src/main/res/layout/activity_take_picture.xml
new file mode 100644
index 000000000..d5daa77cf
--- /dev/null
+++ b/app/src/main/res/layout/activity_take_picture.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_attachment.xml b/app/src/main/res/layout/dialog_attachment.xml
index 719393a94..dbc3332ba 100644
--- a/app/src/main/res/layout/dialog_attachment.xml
+++ b/app/src/main/res/layout/dialog_attachment.xml
@@ -105,6 +105,39 @@
+
+
+
+
+
+
+
+
#606060
+
+ #7f000000
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 807345106..aa22a4de3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -374,6 +374,7 @@
Add to conversation
Upload local file
+ Upload from camera
Share from %1$s
Sorry, upload failed
Choose files
@@ -457,4 +458,9 @@
%1$s (%2$d)
Invalid password
Do you want to reauthorize or delete this account?
+
+ Take a photo
+ Switch camera
+ Toggle torch
+ Error taking picture
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 7b09c2017..029d893be 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -55,6 +55,18 @@
- @color/grey950
+
+
+
+