Clean up after removing ImportAttachmentsWorker.kt
MAILAND-1669
This commit is contained in:
parent
eb5d9d9cd1
commit
5db8f021a9
|
@ -212,11 +212,6 @@
|
|||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activities.AddAttachmentsActivity"
|
||||
android:exported="false"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activities.SearchActivity"
|
||||
android:exported="false"
|
||||
|
|
|
@ -1,614 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail 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.
|
||||
*
|
||||
* ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
package ch.protonmail.android.activities;
|
||||
|
||||
import static ch.protonmail.android.attachments.ImportAttachmentsWorkerKt.KEY_INPUT_DATA_DELETE_ORIGINAL_FILE_BOOLEAN;
|
||||
import static ch.protonmail.android.attachments.ImportAttachmentsWorkerKt.KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import butterknife.BindView;
|
||||
import ch.protonmail.android.R;
|
||||
import ch.protonmail.android.adapters.AttachmentListAdapter;
|
||||
import ch.protonmail.android.attachments.AttachmentsViewModel;
|
||||
import ch.protonmail.android.attachments.AttachmentsViewState;
|
||||
import ch.protonmail.android.attachments.ImportAttachmentsWorker;
|
||||
import ch.protonmail.android.core.Constants;
|
||||
import ch.protonmail.android.core.ProtonMailApplication;
|
||||
import ch.protonmail.android.data.local.MessageDao;
|
||||
import ch.protonmail.android.data.local.MessageDatabase;
|
||||
import ch.protonmail.android.data.local.model.Attachment;
|
||||
import ch.protonmail.android.data.local.model.LocalAttachment;
|
||||
import ch.protonmail.android.events.DownloadedAttachmentEvent;
|
||||
import ch.protonmail.android.events.PostImportAttachmentEvent;
|
||||
import ch.protonmail.android.events.PostImportAttachmentFailureEvent;
|
||||
import ch.protonmail.android.events.Status;
|
||||
import ch.protonmail.android.utils.DateUtil;
|
||||
import ch.protonmail.android.utils.DownloadUtils;
|
||||
import ch.protonmail.android.utils.Logger;
|
||||
import ch.protonmail.android.utils.extensions.TextExtensions;
|
||||
import ch.protonmail.android.utils.ui.dialogs.DialogUtils;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import kotlin.collections.ArraysKt;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import timber.log.Timber;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class AddAttachmentsActivity extends BaseStoragePermissionActivity implements AttachmentListAdapter.IAttachmentListener {
|
||||
|
||||
private static final String TAG_ADD_ATTACHMENTS_ACTIVITY = "AddAttachmentsActivity";
|
||||
public static final String EXTRA_ATTACHMENT_LIST = "EXTRA_ATTACHMENT_LIST";
|
||||
public static final String EXTRA_DRAFT_ID = "EXTRA_DRAFT_ID";
|
||||
public static final String EXTRA_DRAFT_CREATED = "EXTRA_DRAFT_CREATED";
|
||||
private static final String ATTACHMENT_MIME_TYPE = "*/*";
|
||||
private static final int REQUEST_CODE_ATTACH_FILE = 1;
|
||||
private static final int REQUEST_CODE_TAKE_PHOTO = 2;
|
||||
private static final String STATE_PHOTO_PATH = "STATE_PATH_TO_PHOTO";
|
||||
|
||||
private MessageDao messageDao;
|
||||
|
||||
private AttachmentListAdapter mAdapter;
|
||||
@BindView(R.id.progress_layout)
|
||||
View mProgressLayout;
|
||||
@BindView(R.id.processing_attachment_layout)
|
||||
View mProcessingAttachmentLayout;
|
||||
@BindView(R.id.no_attachments)
|
||||
View mNoAttachmentsView;
|
||||
@BindView(R.id.num_attachments)
|
||||
TextView mNumAttachmentsView;
|
||||
@BindView(R.id.attachment_list)
|
||||
ListView mListView;
|
||||
|
||||
@Inject
|
||||
WorkManager workManager;
|
||||
@Inject
|
||||
DownloadUtils downloadUtils;
|
||||
|
||||
private String mPathToPhoto;
|
||||
private String mDraftId;
|
||||
private boolean mDraftCreated;
|
||||
private List<Uri> mAttachFileWithoutPermission;
|
||||
private String mAttachTakePhotoWithoutPermission;
|
||||
private boolean openGallery = false;
|
||||
private boolean openCamera = false;
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_add_attachments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHasPermission(Constants.PermissionType type) {
|
||||
if (type == Constants.PermissionType.STORAGE) {
|
||||
super.onHasPermission(type);
|
||||
mHasStoragePermission = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionDenied(Constants.PermissionType type) {
|
||||
super.onPermissionDenied(type);
|
||||
openCamera = false;
|
||||
openGallery = false;
|
||||
DialogUtils.Companion.showInfoDialog(AddAttachmentsActivity.this, getString(R.string.need_permissions_title),
|
||||
getString(R.string.need_storage_permissions_text), unit -> unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionConfirmed(Constants.PermissionType type) {
|
||||
super.onPermissionConfirmed(type);
|
||||
if (openGallery) {
|
||||
openGallery();
|
||||
}
|
||||
if (openCamera) {
|
||||
openCamera();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void storagePermissionGranted() {
|
||||
mHasStoragePermission = true;
|
||||
if (mAttachFileWithoutPermission != null && mAttachFileWithoutPermission.size() > 0) {
|
||||
mProcessingAttachmentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
String[] uriStrings = new String[mAttachFileWithoutPermission.size()];
|
||||
for (int i = 0; i < mAttachFileWithoutPermission.size(); i++) {
|
||||
uriStrings[i] = mAttachFileWithoutPermission.get(i).toString();
|
||||
}
|
||||
|
||||
Data workerData = new Data.Builder()
|
||||
.putStringArray(KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY, uriStrings)
|
||||
.build();
|
||||
|
||||
OneTimeWorkRequest importAttachmentsWork = new OneTimeWorkRequest.Builder(ImportAttachmentsWorker.class)
|
||||
.setInputData(workerData)
|
||||
.build();
|
||||
|
||||
workManager.enqueue(importAttachmentsWork);
|
||||
|
||||
mAttachFileWithoutPermission = null;
|
||||
}
|
||||
if (!TextUtils.isEmpty(mAttachTakePhotoWithoutPermission)) {
|
||||
mProcessingAttachmentLayout.setVisibility(View.VISIBLE);
|
||||
handleTakePhotoRequest(mAttachTakePhotoWithoutPermission);
|
||||
mAttachTakePhotoWithoutPermission = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkForPermissionOnStartup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
messageDao = MessageDatabase.Factory.getInstance(getApplicationContext(), mUserManager.requireCurrentUserId()).getDao();
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.add_attachment);
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
ArrayList<LocalAttachment> attachmentList = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENT_LIST);
|
||||
if (attachmentList == null) {
|
||||
attachmentList = new ArrayList<>();
|
||||
}
|
||||
mDraftId = intent.getStringExtra(EXTRA_DRAFT_ID);
|
||||
mDraftCreated = intent.getBooleanExtra(EXTRA_DRAFT_CREATED, true);
|
||||
int attachmentsCount = attachmentList.size();
|
||||
int totalEmbeddedImages = countEmbeddedImages(attachmentList);
|
||||
updateAttachmentsCount(attachmentsCount, totalEmbeddedImages);
|
||||
if (mDraftCreated) {
|
||||
mProgressLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
mProgressLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
mAdapter = new AttachmentListAdapter(this, attachmentList, totalEmbeddedImages,
|
||||
workManager);
|
||||
mListView.setAdapter(mAdapter);
|
||||
|
||||
AttachmentsViewModel viewModel = new ViewModelProvider(this).get(AttachmentsViewModel.class);
|
||||
viewModel.init();
|
||||
|
||||
viewModel.getViewState().observe(this, this::viewStateChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
ProtonMailApplication.getApplication().getBus().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
ProtonMailApplication.getApplication().getBus().unregister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_attachments, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.take_photo).setVisible(getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA));
|
||||
menu.findItem(R.id.attach_file).setVisible(mDraftCreated);
|
||||
menu.findItem(R.id.take_photo).setVisible(mDraftCreated);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
saveLastInteraction();
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_DRAFT_ID, mDraftId);
|
||||
ArrayList<LocalAttachment> currentAttachments = mAdapter.getData();
|
||||
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENT_LIST, currentAttachments);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
saveLastInteraction();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
case R.id.attach_file: {
|
||||
openGallery = true;
|
||||
if (mHasStoragePermission != null && mHasStoragePermission) {
|
||||
return openGallery();
|
||||
} else {
|
||||
storagePermissionHelper.checkPermission();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case R.id.take_photo: {
|
||||
openCamera = true;
|
||||
if (mHasStoragePermission != null && mHasStoragePermission) {
|
||||
return openCamera();
|
||||
} else {
|
||||
storagePermissionHelper.checkPermission();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAttachmentsSizeAllowed(long newAttachmentSie) {
|
||||
if (mAdapter == null) return false;
|
||||
|
||||
long currentAttachmentsSize =
|
||||
CollectionsKt.sumOfLong(CollectionsKt.map(mAdapter.getData(), LocalAttachment::getSize));
|
||||
|
||||
return currentAttachmentsSize + newAttachmentSie < Constants.MAX_ATTACHMENT_FILE_SIZE_IN_BYTES;
|
||||
}
|
||||
|
||||
private boolean isAttachmentsCountAllowed() {
|
||||
return mAdapter != null && mAdapter.getCount() < Constants.MAX_ATTACHMENTS;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPostImportAttachmentEvent(PostImportAttachmentEvent event) {
|
||||
mProcessingAttachmentLayout.setVisibility(View.GONE);
|
||||
mNoAttachmentsView.setVisibility(View.GONE);
|
||||
LocalAttachment newAttachment = new LocalAttachment(Uri.parse(event.uri), event.displayName, event.size, event.mimeType);
|
||||
ArrayList<LocalAttachment> currentAttachments = mAdapter.getData();
|
||||
boolean alreadyExists = false;
|
||||
for (LocalAttachment localAttachment : currentAttachments) {
|
||||
String localAttachmentDisplayName = localAttachment.getDisplayName();
|
||||
boolean isEmpty = TextUtils.isEmpty(localAttachmentDisplayName);
|
||||
if (!isEmpty && localAttachmentDisplayName.equals(newAttachment.getDisplayName())) {
|
||||
alreadyExists = true;
|
||||
}
|
||||
}
|
||||
if (alreadyExists) {
|
||||
TextExtensions.showToast(AddAttachmentsActivity.this, R.string.attachment_exists, Toast.LENGTH_SHORT);
|
||||
return;
|
||||
}
|
||||
currentAttachments.add(newAttachment);
|
||||
int totalEmbeddedImages = countEmbeddedImages(currentAttachments);
|
||||
mAdapter.updateData(new ArrayList<>(currentAttachments), totalEmbeddedImages);
|
||||
int attachments = currentAttachments.size();
|
||||
updateAttachmentsCount(attachments, totalEmbeddedImages);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPostImportAttachmentFailureEvent(PostImportAttachmentFailureEvent event) {
|
||||
mProcessingAttachmentLayout.setVisibility(View.GONE);
|
||||
TextExtensions.showToast(this, R.string.problem_selecting_file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode != RESULT_OK) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHasStoragePermission == null || !mHasStoragePermission) {
|
||||
|
||||
if (requestCode == REQUEST_CODE_ATTACH_FILE) {
|
||||
mAttachFileWithoutPermission = new ArrayList<>();
|
||||
ClipData clipData = data.getClipData();
|
||||
if (clipData != null) {
|
||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||
ClipData.Item item = clipData.getItemAt(i);
|
||||
mAttachFileWithoutPermission.add(item.getUri());
|
||||
}
|
||||
} else {
|
||||
mAttachFileWithoutPermission.add(data.getData());
|
||||
}
|
||||
|
||||
} else if (requestCode == REQUEST_CODE_TAKE_PHOTO) {
|
||||
mAttachTakePhotoWithoutPermission = mPathToPhoto;
|
||||
}
|
||||
|
||||
storagePermissionHelper.checkPermission();
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestCode == REQUEST_CODE_ATTACH_FILE) {
|
||||
handleAttachFileRequest(data.getData(), data.getClipData());
|
||||
|
||||
} else if (requestCode == REQUEST_CODE_TAKE_PHOTO) {
|
||||
handleTakePhotoRequest(mPathToPhoto);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
outState.putString(STATE_PHOTO_PATH, mPathToPhoto);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
mPathToPhoto = savedInstanceState.getString(STATE_PHOTO_PATH);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDownloadAttachmentEvent(DownloadedAttachmentEvent event) {
|
||||
//once attachment has been downloaded
|
||||
if (event.getStatus().equals(Status.SUCCESS)) {
|
||||
downloadUtils.viewAttachment(this, event.getFilename(), event.getAttachmentUri());
|
||||
TextExtensions.showToast(this, String.format(getString(R.string.attachment_download_success), event.getFilename()), Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
TextExtensions.showToast(this, String.format(getString(R.string.attachment_download_failed), event.getFilename()), Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
private void viewStateChanged(AttachmentsViewState viewState) {
|
||||
if (viewState instanceof AttachmentsViewState.MissingConnectivity) {
|
||||
onMessageReady();
|
||||
}
|
||||
|
||||
if (viewState instanceof AttachmentsViewState.UpdateAttachments) {
|
||||
onMessageReady();
|
||||
updateDisplayedAttachments(
|
||||
((AttachmentsViewState.UpdateAttachments) viewState).getAttachments()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessageReady() {
|
||||
mDraftCreated = true;
|
||||
mProgressLayout.setVisibility(View.GONE);
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void updateDisplayedAttachments(List<Attachment> attachments) {
|
||||
List<LocalAttachment> localAttachments = new ArrayList<>(
|
||||
LocalAttachment.Companion.createLocalAttachmentList(attachments)
|
||||
);
|
||||
int totalEmbeddedImages = countEmbeddedImages(localAttachments);
|
||||
mAdapter.updateData(new ArrayList(localAttachments), totalEmbeddedImages);
|
||||
}
|
||||
|
||||
|
||||
private void handleAttachFileRequest(Uri uri, ClipData clipData) {
|
||||
String[] uris = null;
|
||||
|
||||
String uriString = uri != null ? uri.toString() : null;
|
||||
if (uriString != null) {
|
||||
uris = new String[]{uriString};
|
||||
}
|
||||
|
||||
if (clipData != null) {
|
||||
uris = new String[clipData.getItemCount()];
|
||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||
uris[i] = clipData.getItemAt(i).getUri().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (uris != null) {
|
||||
|
||||
// region Check whether the size of the attachments is within bounds
|
||||
final AtomicLong incrementalSize = new AtomicLong(0);
|
||||
List<String> sizeComplaintUrisList = ArraysKt.filter(uris, fileUriString -> {
|
||||
Uri fileUri = Uri.parse(fileUriString);
|
||||
|
||||
try {
|
||||
ParcelFileDescriptor fileDescriptor = getContentResolver().openFileDescriptor(fileUri, "r");
|
||||
if (fileDescriptor == null) {
|
||||
return false;
|
||||
}
|
||||
final long fileSize = fileDescriptor.getStatSize();
|
||||
return isAttachmentsSizeAllowed(incrementalSize.addAndGet(fileSize));
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (sizeComplaintUrisList.size() < uris.length) {
|
||||
TextExtensions.showToast(this, R.string.max_attachments_size_reached);
|
||||
}
|
||||
// endregion
|
||||
|
||||
if (sizeComplaintUrisList.size() > 0) {
|
||||
mProcessingAttachmentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
String[] sizeComplaintUris = new String[sizeComplaintUrisList.size()];
|
||||
sizeComplaintUrisList.toArray(sizeComplaintUris);
|
||||
Data workerData = new Data.Builder()
|
||||
.putStringArray(KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY, sizeComplaintUris)
|
||||
.build();
|
||||
|
||||
OneTimeWorkRequest importAttachmentsWork = new OneTimeWorkRequest.Builder(ImportAttachmentsWorker.class)
|
||||
.setInputData(workerData)
|
||||
.build();
|
||||
|
||||
workManager.enqueue(importAttachmentsWork);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTakePhotoRequest(String path) {
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
|
||||
File file = new File(path);
|
||||
|
||||
// Check whether the size of the attachment is within bounds
|
||||
if (!isAttachmentsSizeAllowed(file.length())) {
|
||||
TextExtensions.showToast(this, R.string.max_attachments_size_reached);
|
||||
return;
|
||||
}
|
||||
|
||||
mProcessingAttachmentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
Uri uri = Uri.fromFile(file);
|
||||
Data data = new Data.Builder()
|
||||
.putStringArray(KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY, new String[]{uri.toString()})
|
||||
.putBoolean(KEY_INPUT_DATA_DELETE_ORIGINAL_FILE_BOOLEAN, true)
|
||||
.build();
|
||||
|
||||
OneTimeWorkRequest importAttachmentsWork = new OneTimeWorkRequest.Builder(ImportAttachmentsWorker.class)
|
||||
.setInputData(data)
|
||||
.build();
|
||||
|
||||
workManager.enqueue(importAttachmentsWork);
|
||||
|
||||
workManager.getWorkInfoByIdLiveData(importAttachmentsWork.getId()).observe(this, workInfo -> {
|
||||
if (workInfo != null) {
|
||||
Timber.d("ImportAttachmentsWorker workInfo = " + workInfo.getState());
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
TextExtensions.showToast(this, R.string.attaching_photo_failed, Toast.LENGTH_LONG, Gravity.CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachmentDeleted(int remainingAttachments, int embeddedImagesCount) {
|
||||
updateAttachmentsCount(remainingAttachments, embeddedImagesCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void askStoragePermission() {
|
||||
storagePermissionHelper.checkPermission();
|
||||
}
|
||||
|
||||
private void updateAttachmentsCount(int totalAttachmentsCount, int totalEmbeddedImagesCount) {
|
||||
if (totalAttachmentsCount == 0 && totalEmbeddedImagesCount == 0) {
|
||||
mNoAttachmentsView.postDelayed(() -> mNoAttachmentsView.setVisibility(View.VISIBLE), 350);
|
||||
mNumAttachmentsView.setVisibility(View.GONE);
|
||||
} else {
|
||||
int normalAttachments = totalAttachmentsCount - totalEmbeddedImagesCount;
|
||||
if (normalAttachments > 0) {
|
||||
mNumAttachmentsView.setText(getResources().getQuantityString(R.plurals.attachments, normalAttachments, normalAttachments));
|
||||
mNumAttachmentsView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mNumAttachmentsView.setText(getString(R.string.no_attachments));
|
||||
mNumAttachmentsView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO extract logic to separate class as it is not dependant on activity
|
||||
private int countEmbeddedImages(List<LocalAttachment> attachments) {
|
||||
int embeddedImages = 0;
|
||||
for (LocalAttachment localAttachment : attachments) {
|
||||
if (localAttachment.isEmbeddedImage()) {
|
||||
embeddedImages++;
|
||||
}
|
||||
}
|
||||
return embeddedImages;
|
||||
}
|
||||
|
||||
private boolean openGallery() {
|
||||
if (!isAttachmentsCountAllowed()) {
|
||||
TextExtensions.showToast(this, R.string.max_attachments_reached);
|
||||
return true;
|
||||
}
|
||||
Intent target = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
target.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||
target.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
target.setType(ATTACHMENT_MIME_TYPE);
|
||||
Intent intent = Intent.createChooser(target, getString(R.string.select_file));
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivityForResult(intent, REQUEST_CODE_ATTACH_FILE);
|
||||
} else {
|
||||
TextExtensions.showToast(this, R.string.no_application_found);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean openCamera() {
|
||||
if (!isAttachmentsCountAllowed()) {
|
||||
TextExtensions.showToast(this, R.string.max_attachments_reached);
|
||||
return true;
|
||||
}
|
||||
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
String timestamp = DateUtil.generateTimestamp();
|
||||
timestamp = timestamp.replace("-", "");
|
||||
timestamp = timestamp.replaceAll("[^A-Za-z0-9]", "");
|
||||
File directory = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||
try {
|
||||
if (timestamp.length() < 3) {
|
||||
Random random = new Random();
|
||||
int number = random.nextInt(99999) + 100;
|
||||
timestamp = timestamp + String.valueOf(number);
|
||||
}
|
||||
File file = File.createTempFile(timestamp, ".jpg", directory);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(AddAttachmentsActivity.this, getApplicationContext().getPackageName() + ".provider", file));
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
mPathToPhoto = file.getAbsolutePath();
|
||||
startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
|
||||
} catch (IOException ioe) {
|
||||
Logger.doLogException(TAG_ADD_ATTACHMENTS_ACTIVITY,
|
||||
"Exception creating temporary file for photo", ioe);
|
||||
TextExtensions.showToast(this, R.string.problem_taking_photo);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -18,11 +18,6 @@
|
|||
*/
|
||||
package ch.protonmail.android.activities;
|
||||
|
||||
import static ch.protonmail.android.settings.pin.ValidatePinActivityKt.EXTRA_FRAGMENT_TITLE;
|
||||
import static ch.protonmail.android.settings.pin.ValidatePinActivityKt.EXTRA_PIN_VALID;
|
||||
import static ch.protonmail.android.worker.FetchUserWorkerKt.FETCH_USER_INFO_WORKER_NAME;
|
||||
import static ch.protonmail.android.worker.FetchUserWorkerKt.FETCH_USER_INFO_WORKER_RESULT;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
|
@ -79,6 +74,11 @@ import ch.protonmail.android.worker.FetchUserWorker;
|
|||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static ch.protonmail.android.settings.pin.ValidatePinActivityKt.EXTRA_FRAGMENT_TITLE;
|
||||
import static ch.protonmail.android.settings.pin.ValidatePinActivityKt.EXTRA_PIN_VALID;
|
||||
import static ch.protonmail.android.worker.FetchUserWorkerKt.FETCH_USER_INFO_WORKER_NAME;
|
||||
import static ch.protonmail.android.worker.FetchUserWorkerKt.FETCH_USER_INFO_WORKER_RESULT;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public abstract class BaseActivity extends AppCompatActivity implements INetworkConfiguratorCallback {
|
||||
|
||||
|
@ -373,10 +373,6 @@ public abstract class BaseActivity extends AppCompatActivity implements INetwork
|
|||
if (!validationCanceled && !(this instanceof ValidatePinActivity)) {
|
||||
saveLastInteraction();
|
||||
}
|
||||
if (!(this instanceof AddAttachmentsActivity)) {
|
||||
inApp = false;
|
||||
activateScreenProtector();
|
||||
}
|
||||
}
|
||||
|
||||
private void deactivateScreenProtector() {
|
||||
|
|
|
@ -40,12 +40,10 @@ import android.text.TextUtils;
|
|||
import android.text.TextWatcher;
|
||||
import android.text.format.Formatter;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.webkit.WebSettings;
|
||||
|
@ -68,10 +66,6 @@ import androidx.core.widget.NestedScrollView;
|
|||
import androidx.lifecycle.Observer;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
@ -92,7 +86,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Inject;
|
||||
|
@ -100,7 +93,6 @@ import javax.inject.Inject;
|
|||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
import ch.protonmail.android.R;
|
||||
import ch.protonmail.android.activities.AddAttachmentsActivity;
|
||||
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository;
|
||||
import ch.protonmail.android.api.models.MessageRecipient;
|
||||
import ch.protonmail.android.api.models.SendPreference;
|
||||
|
@ -109,7 +101,6 @@ import ch.protonmail.android.api.models.address.Address;
|
|||
import ch.protonmail.android.api.models.enumerations.MessageEncryption;
|
||||
import ch.protonmail.android.api.segments.event.AlarmReceiver;
|
||||
import ch.protonmail.android.attachments.DownloadEmbeddedAttachmentsWorker;
|
||||
import ch.protonmail.android.attachments.ImportAttachmentsWorker;
|
||||
import ch.protonmail.android.compose.ComposeMessageViewModel;
|
||||
import ch.protonmail.android.compose.presentation.ui.MessageRecipientArrayAdapter;
|
||||
import ch.protonmail.android.compose.presentation.ui.ComposeMessageKotlinActivity;
|
||||
|
@ -130,7 +121,6 @@ import ch.protonmail.android.events.DownloadEmbeddedImagesEvent;
|
|||
import ch.protonmail.android.events.FetchDraftDetailEvent;
|
||||
import ch.protonmail.android.events.FetchMessageDetailEvent;
|
||||
import ch.protonmail.android.events.MessageSavedEvent;
|
||||
import ch.protonmail.android.events.PostImportAttachmentEvent;
|
||||
import ch.protonmail.android.events.PostLoadContactsEvent;
|
||||
import ch.protonmail.android.events.ResignContactEvent;
|
||||
import ch.protonmail.android.events.Status;
|
||||
|
@ -155,7 +145,6 @@ import ch.protonmail.android.utils.ServerTime;
|
|||
import ch.protonmail.android.utils.UiUtil;
|
||||
import ch.protonmail.android.utils.crypto.TextDecryptionResult;
|
||||
import ch.protonmail.android.utils.extensions.CommonExtensionsKt;
|
||||
import ch.protonmail.android.utils.extensions.SerializationUtils;
|
||||
import ch.protonmail.android.utils.extensions.TextExtensions;
|
||||
import ch.protonmail.android.utils.ui.dialogs.DialogUtils;
|
||||
import ch.protonmail.android.utils.ui.locks.ComposerLockIcon;
|
||||
|
@ -164,15 +153,9 @@ import ch.protonmail.android.views.MessagePasswordButton;
|
|||
import ch.protonmail.android.views.MessageRecipientView;
|
||||
import ch.protonmail.android.views.PMWebViewClient;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import kotlin.Unit;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import kotlin.jvm.functions.Function0;
|
||||
import me.proton.core.accountmanager.domain.AccountManager;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static ch.protonmail.android.attachments.ImportAttachmentsWorkerKt.KEY_INPUT_DATA_COMPOSER_INSTANCE_ID;
|
||||
import static ch.protonmail.android.attachments.ImportAttachmentsWorkerKt.KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY;
|
||||
import static ch.protonmail.android.settings.pin.ValidatePinActivityKt.EXTRA_ATTACHMENT_IMPORT_EVENT;
|
||||
import static ch.protonmail.android.settings.pin.ValidatePinActivityKt.EXTRA_DRAFT_DETAILS_EVENT;
|
||||
import static ch.protonmail.android.settings.pin.ValidatePinActivityKt.EXTRA_MESSAGE_DETAIL_EVENT;
|
||||
|
||||
|
@ -207,7 +190,6 @@ public class ComposeMessageActivity
|
|||
public static final String EXTRA_REPLY_FROM_GCM = "reply_from_gcm";
|
||||
public static final String EXTRA_LOAD_IMAGES = "load_images";
|
||||
public static final String EXTRA_LOAD_REMOTE_CONTENT = "load_remote_content";
|
||||
private static final int REQUEST_CODE_ADD_ATTACHMENTS = 1;
|
||||
private static final String STATE_ATTACHMENT_LIST = "attachment_list";
|
||||
private static final String STATE_ADDITIONAL_ROWS_VISIBLE = "additional_rows_visible";
|
||||
private static final String STATE_DIRTY = "dirty";
|
||||
|
@ -260,8 +242,6 @@ public class ComposeMessageActivity
|
|||
@Inject
|
||||
DownloadEmbeddedAttachmentsWorker.Enqueuer attachmentsWorker;
|
||||
|
||||
String composerInstanceId;
|
||||
|
||||
Menu menu;
|
||||
|
||||
@Override
|
||||
|
@ -680,15 +660,6 @@ public class ComposeMessageActivity
|
|||
addRecipientsToView(recipients, recipient);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Function0<Unit> onConnectivityCheckRetry() {
|
||||
return () -> {
|
||||
networkSnackBarUtil.getCheckingConnectionSnackBar(mSnackLayout, null).show();
|
||||
composeMessageViewModel.checkConnectivityDelayed();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private void onConnectivityEvent(Constants.ConnectionState connectivity) {
|
||||
Timber.v("onConnectivityEvent hasConnectivity:%s DoHOngoing:%s", connectivity.name(), isDohOngoing);
|
||||
if (!isDohOngoing) {
|
||||
|
@ -792,34 +763,7 @@ public class ComposeMessageActivity
|
|||
|
||||
private void handleSendFileUri(Uri uri) {
|
||||
if (uri != null) {
|
||||
composerInstanceId = UUID.randomUUID().toString();
|
||||
Data data = new Data.Builder()
|
||||
.putStringArray(KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY, new String[]{uri.toString()})
|
||||
.putString(KEY_INPUT_DATA_COMPOSER_INSTANCE_ID, composerInstanceId)
|
||||
.build();
|
||||
OneTimeWorkRequest importAttachmentsWork = new OneTimeWorkRequest.Builder(ImportAttachmentsWorker.class)
|
||||
.setInputData(data)
|
||||
.build();
|
||||
WorkManager workManager = WorkManager.getInstance();
|
||||
workManager.enqueue(importAttachmentsWork);
|
||||
|
||||
// Observe the Worker with a LiveData, because result will be received when the
|
||||
// Activity will back in foreground, since an EventBut event would be lost while in
|
||||
// Background
|
||||
workManager.getWorkInfoByIdLiveData(importAttachmentsWork.getId())
|
||||
.observe(this, workInfo -> {
|
||||
if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED) {
|
||||
|
||||
// Get the Event from Worker
|
||||
String json = workInfo.getOutputData().getString(composerInstanceId);
|
||||
if (json != null) {
|
||||
PostImportAttachmentEvent event = SerializationUtils.deserialize(
|
||||
json, PostImportAttachmentEvent.class
|
||||
);
|
||||
onPostImportAttachmentEvent(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
composeMessageViewModel.addAttachment(uri, false);
|
||||
composeMessageViewModel.setIsDirty(true);
|
||||
}
|
||||
}
|
||||
|
@ -827,42 +771,7 @@ public class ComposeMessageActivity
|
|||
void handleSendMultipleFiles(Intent intent) {
|
||||
ArrayList<Uri> fileUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
if (fileUris != null) {
|
||||
String[] uriStrings = new String[fileUris.size()];
|
||||
for (int i = 0; i < fileUris.size(); i++) {
|
||||
uriStrings[i] = fileUris.get(i).toString();
|
||||
}
|
||||
composerInstanceId = UUID.randomUUID().toString();
|
||||
Data data = new Data.Builder()
|
||||
.putStringArray(KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY, uriStrings)
|
||||
.putString(KEY_INPUT_DATA_COMPOSER_INSTANCE_ID, composerInstanceId)
|
||||
.build();
|
||||
OneTimeWorkRequest importAttachmentsWork = new OneTimeWorkRequest.Builder(ImportAttachmentsWorker.class)
|
||||
.setInputData(data)
|
||||
.build();
|
||||
WorkManager.getInstance().enqueue(importAttachmentsWork);
|
||||
WorkManager.getInstance().getWorkInfoByIdLiveData(importAttachmentsWork.getId()).observe(this, workInfo -> {
|
||||
if (workInfo != null) {
|
||||
Log.d("PMTAG", "ImportAttachmentsWorker workInfo = " + workInfo.getState());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPostImportAttachmentEvent(PostImportAttachmentEvent event) {
|
||||
if (event == null || event.composerInstanceId == null || !event.composerInstanceId.equals(composerInstanceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<LocalAttachment> attachmentsList = composeMessageViewModel.getMessageDataResult().getAttachmentList();
|
||||
boolean alreadyAdded = CollectionsKt.firstOrNull(attachmentsList, localAttachment ->
|
||||
localAttachment.getUri().toString().equals(event.uri)
|
||||
) != null;
|
||||
|
||||
if (!alreadyAdded) {
|
||||
composeMessageViewModel.setIsDirty(true);
|
||||
attachmentsList.add(new LocalAttachment(Uri.parse(event.uri), event.displayName, event.size, event.mimeType));
|
||||
renderViews();
|
||||
composeMessageViewModel.addAttachments(fileUris, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1286,25 +1195,9 @@ public class ComposeMessageActivity
|
|||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == REQUEST_CODE_ADD_ATTACHMENTS && resultCode == RESULT_OK) {
|
||||
Timber.d("ComposeMessageAct.onActivityResult Received add attachment response with result OK");
|
||||
askForPermission = false;
|
||||
ArrayList<LocalAttachment> resultAttachmentList = data.getParcelableArrayListExtra(AddAttachmentsActivity.EXTRA_ATTACHMENT_LIST);
|
||||
ArrayList<LocalAttachment> listToSet = resultAttachmentList != null ? resultAttachmentList : new ArrayList<>();
|
||||
composeMessageViewModel.setAttachmentList(listToSet);
|
||||
composeMessageViewModel.setIsDirty(true);
|
||||
String oldDraftId = composeMessageViewModel.getDraftId();
|
||||
afterAttachmentsAdded();
|
||||
composeMessageViewModel.setIsDirty(true);
|
||||
} else if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_VALIDATE_PIN) {
|
||||
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_VALIDATE_PIN) {
|
||||
// region pin results
|
||||
if (data.hasExtra(EXTRA_ATTACHMENT_IMPORT_EVENT)) {
|
||||
Object attachmentExtra = data.getSerializableExtra(EXTRA_ATTACHMENT_IMPORT_EVENT);
|
||||
if (attachmentExtra instanceof PostImportAttachmentEvent) {
|
||||
onPostImportAttachmentEvent((PostImportAttachmentEvent) attachmentExtra);
|
||||
}
|
||||
composeMessageViewModel.setBeforeSaveDraft(false, messageBodyEditText.getText().toString());
|
||||
} else if (data.hasExtra(EXTRA_MESSAGE_DETAIL_EVENT) || data.hasExtra(EXTRA_DRAFT_DETAILS_EVENT)) {
|
||||
if (data.hasExtra(EXTRA_MESSAGE_DETAIL_EVENT) || data.hasExtra(EXTRA_DRAFT_DETAILS_EVENT)) {
|
||||
FetchMessageDetailEvent messageDetailEvent = (FetchMessageDetailEvent) data.getSerializableExtra(EXTRA_MESSAGE_DETAIL_EVENT);
|
||||
FetchDraftDetailEvent draftDetailEvent = (FetchDraftDetailEvent) data.getSerializableExtra(EXTRA_DRAFT_DETAILS_EVENT);
|
||||
if (messageDetailEvent != null) {
|
||||
|
@ -1316,18 +1209,11 @@ public class ComposeMessageActivity
|
|||
}
|
||||
toRecipientView.requestFocus();
|
||||
UiUtil.toggleKeyboard(this, toRecipientView);
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
// endregion
|
||||
} else {
|
||||
Timber.w("ComposeMessageAct.onActivityResult Received result not handled", requestCode, resultCode);
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void afterAttachmentsAdded() {
|
||||
composeMessageViewModel.setBeforeSaveDraft(false, messageBodyEditText.getText().toString());
|
||||
composeMessageViewModel.setIsDirty(true);
|
||||
renderViews();
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
@ -1813,16 +1699,6 @@ public class ComposeMessageActivity
|
|||
}
|
||||
}
|
||||
|
||||
private class ParentViewScrollListener implements View.OnTouchListener {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
|
||||
messageBodyEditText.setFocusableInTouchMode(true);
|
||||
messageBodyEditText.requestFocus();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class RespondInlineButtonClickListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail 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.
|
||||
*
|
||||
* ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
package ch.protonmail.android.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.format.Formatter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.work.WorkManager
|
||||
import ch.protonmail.android.R
|
||||
import ch.protonmail.android.data.local.model.LocalAttachment
|
||||
import ch.protonmail.android.worker.DeleteAttachmentWorker
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import java.util.Comparator
|
||||
|
||||
|
||||
class AttachmentListAdapter(
|
||||
context: Context,
|
||||
attachmentsList: List<LocalAttachment>?,
|
||||
private var numberOfEmbeddedImages: Int,
|
||||
private val workManager: WorkManager
|
||||
) : ArrayAdapter<LocalAttachment>(context, 0, attachmentsList ?: emptyList()) {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private val listener: IAttachmentListener
|
||||
|
||||
private val attachmentSortComparator = Comparator<LocalAttachment> { lhs, rhs ->
|
||||
val embeddedImageCompare = java.lang.Boolean.valueOf(lhs.isEmbeddedImage).compareTo(rhs.isEmbeddedImage)
|
||||
if (embeddedImageCompare != 0) {
|
||||
embeddedImageCompare
|
||||
} else lhs.displayName.compareTo(rhs.displayName, ignoreCase = true)
|
||||
}
|
||||
|
||||
private var attachmentsList = attachmentsList?.sortedWith(attachmentSortComparator) ?: emptyList()
|
||||
|
||||
val data: ArrayList<LocalAttachment>
|
||||
get() = ArrayList(attachmentsList)
|
||||
|
||||
init {
|
||||
listener = context as IAttachmentListener
|
||||
sort(attachmentSortComparator)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun updateData(attachmentList: ArrayList<LocalAttachment>, numEmbeddedImages: Int) {
|
||||
clear()
|
||||
numberOfEmbeddedImages = numEmbeddedImages
|
||||
attachmentsList = attachmentList.sortedWith(attachmentSortComparator)
|
||||
addAll(attachmentsList)
|
||||
sort(attachmentSortComparator)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
var view = convertView
|
||||
val attachment = getItem(position)
|
||||
var previousAttachment: LocalAttachment? = null
|
||||
if (position > 0) {
|
||||
previousAttachment = getItem(position - 1)
|
||||
}
|
||||
view = if (attachment!!.isEmbeddedImage && (position == 0 || !previousAttachment!!.isEmbeddedImage)) {
|
||||
inflater.inflate(R.layout.attachment_inline_first_list_item, parent, false)
|
||||
} else {
|
||||
inflater.inflate(R.layout.attachment_list_item, parent, false)
|
||||
}
|
||||
|
||||
val attachmentName = view.findViewById<TextView>(R.id.attachment_name)
|
||||
val attachmentSize = view.findViewById<TextView>(R.id.attachment_size)
|
||||
val embeddedImageHeader = view.findViewById<TextView>(R.id.num_embedded_images_attachments)
|
||||
val embeddedImagePrefix = view.findViewById<TextView>(R.id.embedded_image_attachment)
|
||||
val removeButton = view.findViewById<ImageButton>(R.id.remove)
|
||||
|
||||
if (embeddedImageHeader != null && attachment.isEmbeddedImage) {
|
||||
embeddedImageHeader.visibility = View.VISIBLE
|
||||
embeddedImageHeader.text = String.format(context.getString(R.string.inline_header), numberOfEmbeddedImages)
|
||||
} else if (embeddedImageHeader != null) {
|
||||
embeddedImageHeader.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (attachment.isEmbeddedImage) {
|
||||
embeddedImagePrefix.visibility = View.VISIBLE
|
||||
} else {
|
||||
embeddedImagePrefix.visibility = View.GONE
|
||||
}
|
||||
|
||||
attachmentName.text = attachment.displayName
|
||||
attachmentSize.text = context.getString(R.string.attachment_size, Formatter.formatShortFileSize(context, attachment.size))
|
||||
|
||||
removeButton.setOnClickListener {
|
||||
val isEmbedded = attachment.isEmbeddedImage
|
||||
remove(attachment)
|
||||
attachmentsList = attachmentsList.filterNot {
|
||||
attachment.attachmentId.isNotEmpty() &&
|
||||
it.attachmentId == attachment.attachmentId ||
|
||||
attachment.attachmentId.isEmpty() &&
|
||||
attachment.displayName == it.displayName
|
||||
}
|
||||
if (isEmbedded) {
|
||||
numberOfEmbeddedImages -= 1
|
||||
}
|
||||
listener.onAttachmentDeleted(count, numberOfEmbeddedImages)
|
||||
|
||||
DeleteAttachmentWorker.Enqueuer(workManager).enqueue(attachment.attachmentId)
|
||||
}
|
||||
|
||||
attachmentName.setOnClickListener {
|
||||
if ("file" == attachment.uri.scheme) {
|
||||
val localFileUri = FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", File(attachment.uri.path))
|
||||
val intent = Intent(Intent.ACTION_VIEW).setDataAndType(localFileUri, attachment.mimeType).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
interface IAttachmentListener {
|
||||
fun onAttachmentDeleted(remainingAttachments: Int, embeddedImagesCount: Int)
|
||||
fun askStoragePermission()
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail 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.
|
||||
*
|
||||
* ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.attachments
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import ch.protonmail.android.activities.AddAttachmentsActivity.EXTRA_DRAFT_ID
|
||||
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
|
||||
import ch.protonmail.android.core.NetworkConnectivityManager
|
||||
import ch.protonmail.android.data.local.model.Message
|
||||
import ch.protonmail.android.utils.MessageUtils
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.core.util.kotlin.DispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AttachmentsViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val dispatchers: DispatcherProvider,
|
||||
private val messageDetailsRepository: MessageDetailsRepository,
|
||||
private val networkConnectivityManager: NetworkConnectivityManager
|
||||
) : ViewModel() {
|
||||
|
||||
val viewState: MutableLiveData<AttachmentsViewState> = MutableLiveData()
|
||||
|
||||
fun init() {
|
||||
viewModelScope.launch(dispatchers.Io) {
|
||||
val messageId = savedStateHandle.get<String>(EXTRA_DRAFT_ID) ?: return@launch
|
||||
val message = messageDetailsRepository.findMessageById(messageId).first()
|
||||
|
||||
message?.let { existingMessage ->
|
||||
val messageDbId = requireNotNull(existingMessage.dbId)
|
||||
val messageFlow = messageDetailsRepository.findMessageByDbId(messageDbId)
|
||||
|
||||
if (!networkConnectivityManager.isInternetConnectionPossible()) {
|
||||
viewState.postValue(AttachmentsViewState.MissingConnectivity)
|
||||
}
|
||||
|
||||
messageFlow.collect { updatedMessage ->
|
||||
if (updatedMessage == null) {
|
||||
return@collect
|
||||
}
|
||||
if (!this.isActive) {
|
||||
return@collect
|
||||
}
|
||||
if (draftCreationHappened(existingMessage, updatedMessage)) {
|
||||
viewState.postValue(AttachmentsViewState.UpdateAttachments(updatedMessage.Attachments))
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun draftCreationHappened(existingMessage: Message, updatedMessage: Message) =
|
||||
!isRemoteMessage(existingMessage) && isRemoteMessage(updatedMessage)
|
||||
|
||||
private fun isRemoteMessage(message: Message) = !MessageUtils.isLocalMessageId(message.messageId)
|
||||
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail 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.
|
||||
*
|
||||
* ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
package ch.protonmail.android.attachments
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.provider.OpenableColumns
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import ch.protonmail.android.core.Constants
|
||||
import ch.protonmail.android.events.PostImportAttachmentEvent
|
||||
import ch.protonmail.android.events.PostImportAttachmentFailureEvent
|
||||
import ch.protonmail.android.utils.AppUtil
|
||||
import ch.protonmail.android.utils.Logger
|
||||
import ch.protonmail.android.utils.extensions.serialize
|
||||
import java.io.File
|
||||
|
||||
// region constants
|
||||
const val KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY = "KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY"
|
||||
const val KEY_INPUT_DATA_DELETE_ORIGINAL_FILE_BOOLEAN = "KEY_INPUT_DATA_DELETE_ORIGINAL_FILE_BOOLEAN"
|
||||
const val KEY_INPUT_DATA_COMPOSER_INSTANCE_ID = "KEY_INPUT_DATA_COMPOSER_INSTANCE_ID"
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* Represents one unit of work importing attachments to app's cache directory.
|
||||
*
|
||||
* InputData has to contain non-null values for:
|
||||
* - fileUris
|
||||
*
|
||||
* Optionally:
|
||||
* - deleteOriginalFile: if Uri's scheme is [ContentResolver.SCHEME_FILE], delete this file after import
|
||||
*
|
||||
* OutputData contains:
|
||||
* TODO when we move from EventBus to observing Workers
|
||||
*
|
||||
* @see androidx.work.WorkManager
|
||||
* @see androidx.work.Data
|
||||
*/
|
||||
|
||||
class ImportAttachmentsWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
|
||||
val fileUris = inputData.getStringArray(KEY_INPUT_DATA_FILE_URIS_STRING_ARRAY)?.mapNotNull { Uri.parse(it) } ?: return Result.failure()
|
||||
val deleteOriginalFile = inputData.getBoolean(KEY_INPUT_DATA_DELETE_ORIGINAL_FILE_BOOLEAN, false)
|
||||
val composerInstanceId = inputData.getString(KEY_INPUT_DATA_COMPOSER_INSTANCE_ID)
|
||||
|
||||
val postImportAttachmentEvents = mutableListOf<PostImportAttachmentEvent>()
|
||||
val contentResolver = applicationContext.contentResolver
|
||||
|
||||
fileUris.filterNot { it.scheme == "file" && (it.path ?: "").contains(applicationContext.applicationInfo.dataDir) }.forEach { uri ->
|
||||
try {
|
||||
contentResolver.openInputStream(uri)?.let {
|
||||
AppUtil.createTempFileFromInputStream(applicationContext, it)?.let { importedFile ->
|
||||
|
||||
var displayName = ""
|
||||
var size: Long = 0
|
||||
var mimeType: String? = ""
|
||||
|
||||
when (uri.scheme) {
|
||||
ContentResolver.SCHEME_CONTENT -> {
|
||||
mimeType = contentResolver.getType(uri)
|
||||
val cursor = contentResolver.query(uri, null, null, null, null)
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
displayName = try {
|
||||
cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
||||
} catch (e: java.lang.Exception) {
|
||||
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)).split("/")[cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)).split("/").size - 1]
|
||||
}
|
||||
size = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE))
|
||||
size = if (size > 0) size else importedFile.length()
|
||||
}
|
||||
|
||||
if (cursor != null && !cursor.isClosed) {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
ContentResolver.SCHEME_FILE -> {
|
||||
val file = File(uri.path)
|
||||
displayName = file.name
|
||||
size = file.length()
|
||||
val extension = MimeTypeMap.getFileExtensionFromUrl(file.name)
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
|
||||
if (deleteOriginalFile) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postImportAttachmentEvents.add(PostImportAttachmentEvent(Uri.fromFile(importedFile), displayName, size, mimeType ?: Constants.MIME_TYPE_UNKNOWN_FILE, composerInstanceId))
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Logger.doLogException(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (postImportAttachmentEvents.isEmpty()) {
|
||||
AppUtil.postEventOnUi(PostImportAttachmentFailureEvent())
|
||||
} else {
|
||||
postImportAttachmentEvents.forEach {
|
||||
AppUtil.postEventOnUi(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Mapping the import events by their `composerInstanceId`
|
||||
val outputEventPairs = postImportAttachmentEvents
|
||||
.map { (it.composerInstanceId ?: "") to it.serialize() }
|
||||
return Result.success(workDataOf(*outputEventPairs.toTypedArray()))
|
||||
}
|
||||
}
|
|
@ -131,7 +131,6 @@ class ComposeMessageViewModel @Inject constructor(
|
|||
private val _deleteResult: MutableLiveData<Event<PostResult>> = MutableLiveData()
|
||||
private val _loadingDraftResult: MutableLiveData<Message> = MutableLiveData()
|
||||
private val _messageResultError: MutableLiveData<Event<PostResult>> = MutableLiveData()
|
||||
private val _openAttachmentsScreenResult: MutableLiveData<List<LocalAttachment>> = MutableLiveData()
|
||||
private val _buildingMessageCompleted: MutableLiveData<Event<Message>> = MutableLiveData()
|
||||
private val _dbIdWatcher: MutableLiveData<Long> = MutableLiveData()
|
||||
private val _fetchMessageDetailsEvent: MutableLiveData<Event<MessageBuilderData>> = MutableLiveData()
|
||||
|
@ -674,35 +673,6 @@ class ComposeMessageViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fun openAttachmentsScreen() {
|
||||
val oldList = _messageDataResult.attachmentList
|
||||
|
||||
viewModelScope.launch {
|
||||
if (draftId.isNotEmpty()) {
|
||||
val message = composeMessageRepository.findMessage(draftId)
|
||||
|
||||
if (message != null) {
|
||||
val messageAttachments =
|
||||
composeMessageRepository.getAttachments(message, dispatchers.Io)
|
||||
if (oldList.size <= messageAttachments.size) {
|
||||
val attachments = LocalAttachment.createLocalAttachmentList(messageAttachments)
|
||||
_messageDataResult = MessageBuilderData.Builder()
|
||||
.fromOld(_messageDataResult)
|
||||
.attachmentList(ArrayList(attachments))
|
||||
.build()
|
||||
_openAttachmentsScreenResult.postValue(attachments)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
_messageDataResult = MessageBuilderData.Builder()
|
||||
.fromOld(_messageDataResult)
|
||||
.attachmentList(ArrayList(oldList))
|
||||
.build()
|
||||
_openAttachmentsScreenResult.postValue(oldList)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteDraft() {
|
||||
viewModelScope.launch {
|
||||
if (_draftId.get().isNotEmpty()) {
|
||||
|
@ -1304,6 +1274,10 @@ class ComposeMessageViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun addAttachment(uri: Uri, deleteOriginalFile: Boolean) {
|
||||
addAttachments(listOf(uri), deleteOriginalFile)
|
||||
}
|
||||
|
||||
fun removeAttachment(uri: Uri) {
|
||||
viewModelScope.launch {
|
||||
imporedAttachments.removeIf { it.originalFileUri == uri }
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail 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.
|
||||
*
|
||||
* ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
package ch.protonmail.android.events;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class PostImportAttachmentEvent implements Serializable {
|
||||
|
||||
public final String uri;
|
||||
public final String displayName;
|
||||
public final long size;
|
||||
public final String mimeType;
|
||||
@Nullable public final String composerInstanceId;
|
||||
|
||||
public PostImportAttachmentEvent(@NonNull Uri uri, @NonNull String displayName, long size, @NonNull String mimeType, String composerInstanceId) {
|
||||
this.uri = uri.toString();
|
||||
this.displayName = displayName;
|
||||
this.size = size;
|
||||
this.mimeType = mimeType;
|
||||
this.composerInstanceId = composerInstanceId;
|
||||
}
|
||||
}
|
|
@ -31,7 +31,6 @@ import ch.protonmail.android.core.ProtonMailApplication
|
|||
import ch.protonmail.android.events.FetchDraftDetailEvent
|
||||
import ch.protonmail.android.events.FetchMessageDetailEvent
|
||||
import ch.protonmail.android.events.MessageCountsEvent
|
||||
import ch.protonmail.android.events.PostImportAttachmentEvent
|
||||
import ch.protonmail.android.settings.pin.viewmodel.PinFragmentViewModel
|
||||
import ch.protonmail.android.utils.AppUtil
|
||||
import ch.protonmail.android.utils.extensions.showToast
|
||||
|
@ -43,22 +42,16 @@ import java.util.concurrent.Executors
|
|||
// region constants
|
||||
const val EXTRA_PIN_VALID = "extra_pin_valid"
|
||||
const val EXTRA_FRAGMENT_TITLE = "extra_title"
|
||||
const val EXTRA_ATTACHMENT_IMPORT_EVENT = "extra_attachment_import_event"
|
||||
const val EXTRA_TOTAL_COUNT_EVENT = "extra_total_count_event"
|
||||
const val EXTRA_MESSAGE_DETAIL_EVENT = "extra_message_details_event"
|
||||
const val EXTRA_DRAFT_DETAILS_EVENT = "extra_draft_details_event"
|
||||
// endregion
|
||||
|
||||
/*
|
||||
* Created by dkadrikj on 3/27/16.
|
||||
*/
|
||||
|
||||
class ValidatePinActivity : BaseActivity(),
|
||||
PinFragmentViewModel.IPinCreationListener,
|
||||
SecureEditText.ISecurePINListener,
|
||||
PinFragmentViewModel.ReopenFingerprintDialogListener {
|
||||
|
||||
private var importAttachmentEvent: PostImportAttachmentEvent? = null
|
||||
private var messageCountsEvent: MessageCountsEvent? = null
|
||||
private var messageDetailEvent: FetchMessageDetailEvent? = null
|
||||
private var draftDetailEvent: FetchDraftDetailEvent? = null
|
||||
|
@ -101,11 +94,6 @@ class ValidatePinActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
// region subscription events
|
||||
@Subscribe
|
||||
fun onPostImportAttachmentEvent(event: PostImportAttachmentEvent) {
|
||||
importAttachmentEvent = event
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onMessageCountsEvent(event: MessageCountsEvent) {
|
||||
messageCountsEvent = event
|
||||
|
@ -217,9 +205,6 @@ class ValidatePinActivity : BaseActivity(),
|
|||
private fun buildIntent(): Intent {
|
||||
return Intent().apply {
|
||||
putExtra(EXTRA_PIN_VALID, true)
|
||||
if (importAttachmentEvent != null) {
|
||||
putExtra(EXTRA_ATTACHMENT_IMPORT_EVENT, importAttachmentEvent)
|
||||
}
|
||||
if (messageCountsEvent != null) {
|
||||
putExtra(EXTRA_TOTAL_COUNT_EVENT, messageCountsEvent)
|
||||
}
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
<!--
|
||||
Copyright (c) 2020 Proton Technologies AG
|
||||
|
||||
This file is part of ProtonMail.
|
||||
|
||||
ProtonMail 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.
|
||||
|
||||
ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
-->
|
||||
|
||||
<RelativeLayout 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/white"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".activities.AddAttachmentsActivity">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar"
|
||||
layout="@layout/toolbar" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/layout_no_connectivity_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/shadow_height"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:background="@drawable/actionbar_shadow" />
|
||||
|
||||
<ch.protonmail.android.views.CustomFontTextView
|
||||
android:id="@+id/no_attachments"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="@dimen/spacing"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_attachments"
|
||||
android:textColor="@color/new_purple"
|
||||
android:textSize="@dimen/h2"
|
||||
android:visibility="gone"
|
||||
app:fontName="Roboto-Thin.ttf" />
|
||||
|
||||
<ch.protonmail.android.views.CustomFontTextView
|
||||
android:id="@+id/num_attachments"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="@dimen/fields_default_space"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/new_purple"
|
||||
android:textSize="@dimen/h2"
|
||||
android:visibility="gone"
|
||||
app:fontName="Roboto-Thin.ttf" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/attachment_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/num_attachments"
|
||||
android:divider="@color/fog_gray"
|
||||
android:dividerHeight="@dimen/divider_height" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/progress_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:background="@color/white"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<ch.protonmail.android.views.CustomFontTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="@dimen/fields_default_space"
|
||||
android:gravity="center"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="@dimen/fields_default_space_small"
|
||||
android:textColor="@color/new_purple"
|
||||
android:textSize="@dimen/h2"
|
||||
android:layout_below="@id/progress_bar"
|
||||
android:text="@string/sync_attachments"
|
||||
app:fontName="Roboto-Thin.ttf" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/processing_attachment_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:background="@color/white"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar_processing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<ch.protonmail.android.views.CustomFontTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="@dimen/fields_default_space"
|
||||
android:gravity="center"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="@dimen/fields_default_space_small"
|
||||
android:textColor="@color/new_purple"
|
||||
android:textSize="@dimen/h2"
|
||||
android:layout_below="@id/progress_bar_processing"
|
||||
android:text="@string/processing_attachment"
|
||||
app:fontName="Roboto-Thin.ttf" />
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
|
@ -1,223 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail 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.
|
||||
*
|
||||
* ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.attachments
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import ch.protonmail.android.activities.AddAttachmentsActivity
|
||||
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
|
||||
import ch.protonmail.android.attachments.AttachmentsViewState.MissingConnectivity
|
||||
import ch.protonmail.android.attachments.AttachmentsViewState.UpdateAttachments
|
||||
import ch.protonmail.android.core.NetworkConnectivityManager
|
||||
import ch.protonmail.android.data.local.model.Attachment
|
||||
import ch.protonmail.android.data.local.model.Message
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.coVerifySequence
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.RelaxedMockK
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import me.proton.core.test.kotlin.CoroutinesTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import kotlin.test.Test
|
||||
|
||||
|
||||
class AttachmentsViewModelTest : CoroutinesTest {
|
||||
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@RelaxedMockK
|
||||
lateinit var messageRepository: MessageDetailsRepository
|
||||
|
||||
@RelaxedMockK
|
||||
lateinit var networkConnectivityManager: NetworkConnectivityManager
|
||||
|
||||
@RelaxedMockK
|
||||
private lateinit var mockObserver: Observer<AttachmentsViewState>
|
||||
|
||||
@RelaxedMockK
|
||||
private lateinit var savedState: SavedStateHandle
|
||||
|
||||
private lateinit var viewModel: AttachmentsViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
viewModel = AttachmentsViewModel(
|
||||
savedState,
|
||||
dispatchers,
|
||||
messageRepository,
|
||||
networkConnectivityManager
|
||||
)
|
||||
viewModel.viewState.observeForever(mockObserver)
|
||||
every { networkConnectivityManager.isInternetConnectionPossible() } returns true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initFindsMessageInDatabase() = runBlockingTest {
|
||||
val messageId = "draftId3214"
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns mockk(relaxed = true)
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
coVerify { messageRepository.findMessageById(messageId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initObservesMessageRepositoryByMessageDbIdWhenGivenMessageIdIsFound() = runBlockingTest {
|
||||
val messageId = "draftId234"
|
||||
val messageDbId = 124L
|
||||
val message = Message(messageId = messageId).apply { dbId = messageDbId }
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns flowOf(message)
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
coVerify { messageRepository.findMessageByDbId(messageDbId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initUpdatesViewStateWhenMessageIsUpdatedInDbAsAResultOfDraftCreationCompleting() = runBlockingTest {
|
||||
val messageId = "91bbb263-2bf2-43dd-a079-233a305e69df"
|
||||
val messageDbId = 124L
|
||||
val message = Message(messageId = messageId).apply { dbId = messageDbId }
|
||||
val updatedMessageAttachments = listOf(Attachment(attachmentId = "updatedAttId"))
|
||||
val remoteMessage = message.copy(messageId = "Remote message id").apply {
|
||||
Attachments = updatedMessageAttachments
|
||||
}
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns flowOf(message)
|
||||
coEvery { messageRepository.findMessageByDbId(messageDbId) } returns flowOf(remoteMessage)
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
val expectedState = UpdateAttachments(updatedMessageAttachments)
|
||||
coVerify { mockObserver.onChanged(expectedState) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initStopsListeningForMessageUpdatesWhenDraftCreationCompletedEventWasReceived() = runBlockingTest {
|
||||
val messageId = "91bbb263-2bf2-43dd-a079-233a305e69df"
|
||||
val messageDbId = 124L
|
||||
val message = Message(messageId = messageId).apply { dbId = messageDbId }
|
||||
val updatedMessageAttachments = listOf(Attachment(attachmentId = "updatedAttId"))
|
||||
val remoteMessage = message.copy(messageId = "Remote message id").apply {
|
||||
Attachments = updatedMessageAttachments
|
||||
}
|
||||
val updatedDraftMessage = remoteMessage.copy(messageId = "Updated remote messageID")
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns flowOf(message)
|
||||
coEvery { messageRepository.findMessageByDbId(messageDbId) } returns flowOf(
|
||||
remoteMessage, updatedDraftMessage
|
||||
)
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
val expectedState = UpdateAttachments(updatedMessageAttachments)
|
||||
coVerifySequence { mockObserver.onChanged(expectedState) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initDoesNotUpdateViewStateWhenMessageIsUpdatedInDbAsAResultOfDraftUpdateCompleting() = runBlockingTest {
|
||||
val messageId = "remote-draft-message ID"
|
||||
val messageDbId = 2384L
|
||||
val message = Message().apply { dbId = messageDbId }
|
||||
val updatedMessageAttachments = listOf(Attachment(attachmentId = "updatedAttId"))
|
||||
val remoteMessage = message.copy(messageId = "Updated Draft Remote message id").apply {
|
||||
Attachments = updatedMessageAttachments
|
||||
}
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns flowOf(message)
|
||||
coEvery { messageRepository.findMessageByDbId(messageDbId) } returns flowOf(remoteMessage)
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
coVerify(exactly = 0) { mockObserver.onChanged(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initDoesNotUpdateViewStateWhenMessageThatWasUpdatedInDbIsNotARemoteMessage() = runBlockingTest {
|
||||
val messageId = "91bbb263-2bf2-43dd-a079-233a305e69df"
|
||||
val messageDbId = 124L
|
||||
val message = Message(messageId = messageId).apply { dbId = messageDbId }
|
||||
val updatedLocalMessage = message.copy(messageId = "82ccc723-2bf2-43dd-f834-233a305e69df")
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns flowOf(message)
|
||||
coEvery { messageRepository.findMessageByDbId(messageDbId) } returns flowOf(updatedLocalMessage)
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
coVerify(exactly = 0) { mockObserver.onChanged(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initPostsOfflineViewStateWhenThereIsNoConnection() = runBlockingTest {
|
||||
val messageId = "91bbb263-2bf2-43dd-a079-233a305e69df"
|
||||
val messageDbId = 124L
|
||||
val message = Message(messageId = messageId).apply { dbId = messageDbId }
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns flowOf(message)
|
||||
coEvery { messageRepository.findMessageByDbId(messageDbId) } returns flowOf()
|
||||
every { networkConnectivityManager.isInternetConnectionPossible() } returns false
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
coVerifySequence { mockObserver.onChanged(MissingConnectivity) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initLogsWarningAndStopsExecutionIfDraftIdWasNotPassed() = runBlockingTest {
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns null
|
||||
|
||||
viewModel.init()
|
||||
|
||||
// test warning is logged here
|
||||
coVerify(exactly = 0) { messageRepository.findMessageByDbId(any()) }
|
||||
coVerify(exactly = 0) { mockObserver.onChanged(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initIgnoresAnyNullMessagesReturnedByDatabaseFlow() = runBlockingTest {
|
||||
val messageId = "91bbb263-2bf2-43dd-a079-233a305e79df"
|
||||
val messageDbId = 113L
|
||||
val message = Message(messageId = messageId).apply { dbId = messageDbId }
|
||||
val updatedMessageAttachments = listOf(Attachment(attachmentId = "updatedAttId"))
|
||||
val remoteMessage = message.copy(messageId = "Remote message id").apply {
|
||||
Attachments = updatedMessageAttachments
|
||||
}
|
||||
coEvery { messageRepository.findMessageById(messageId) } returns flowOf(message)
|
||||
coEvery { messageRepository.findMessageByDbId(messageDbId) } returns flowOf(
|
||||
remoteMessage, null
|
||||
)
|
||||
every { savedState.get<String>(AddAttachmentsActivity.EXTRA_DRAFT_ID) } returns messageId
|
||||
|
||||
viewModel.init()
|
||||
|
||||
val expectedState = UpdateAttachments(updatedMessageAttachments)
|
||||
coVerifySequence { mockObserver.onChanged(expectedState) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue