Folder selection dialog

pull/156/head
M66B 6 years ago
parent faaa1b432d
commit b6ee20e56f

@ -0,0 +1,184 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class AdapterFolderSelect extends RecyclerView.Adapter<AdapterFolderSelect.ViewHolder> {
private Context context;
private LifecycleOwner owner;
private IFolderSelectedListener listener;
private LayoutInflater inflater;
private List<EntityFolder> items = new ArrayList<>();
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private View view;
private ImageView ivType;
private TextView tvName;
ViewHolder(View itemView) {
super(itemView);
view = itemView.findViewById(R.id.clItem);
ivType = itemView.findViewById(R.id.ivType);
tvName = itemView.findViewById(R.id.tvName);
}
private void wire() {
view.setOnClickListener(this);
}
private void unwire() {
view.setOnClickListener(null);
}
private void bindTo(EntityFolder folder) {
ivType.setImageResource(EntityFolder.getIcon(folder.type));
tvName.setText(folder.getDisplayName(context));
}
@Override
public void onClick(View v) {
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
return;
EntityFolder folder = items.get(pos);
if (folder != null)
listener.onFolderSelected(folder);
}
}
AdapterFolderSelect(Context context, LifecycleOwner owner, IFolderSelectedListener listener) {
this.context = context;
this.owner = owner;
this.listener = listener;
this.inflater = LayoutInflater.from(context);
setHasStableIds(true);
}
public void set(@NonNull List<EntityFolder> folders) {
Log.i("Set folders=" + folders.size());
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, folders), false);
items = folders;
diff.dispatchUpdatesTo(new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
Log.i("Inserted @" + position + " #" + count);
}
@Override
public void onRemoved(int position, int count) {
Log.i("Removed @" + position + " #" + count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
Log.i("Moved " + fromPosition + ">" + toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
Log.i("Changed @" + position + " #" + count);
}
});
diff.dispatchUpdatesTo(this);
}
private class DiffCallback extends DiffUtil.Callback {
private List<EntityFolder> prev = new ArrayList<>();
private List<EntityFolder> next = new ArrayList<>();
DiffCallback(List<EntityFolder> prev, List<EntityFolder> next) {
this.prev.addAll(prev);
this.next.addAll(next);
}
@Override
public int getOldListSize() {
return prev.size();
}
@Override
public int getNewListSize() {
return next.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
EntityFolder m1 = prev.get(oldItemPosition);
EntityFolder m2 = next.get(newItemPosition);
return m1.id.equals(m2.id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
EntityFolder m1 = prev.get(oldItemPosition);
EntityFolder m2 = next.get(newItemPosition);
return m1.id.equals(m2.id);
}
}
@Override
public long getItemId(int position) {
return items.get(position).id;
}
@Override
public int getItemCount() {
return items.size();
}
@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.item_folder_select, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
EntityFolder folder = items.get(position);
holder.bindTo(folder);
holder.wire();
}
interface IFolderSelectedListener {
void onFolderSelected(EntityFolder folder);
}
}

@ -2455,6 +2455,66 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}
private void onMenuCopy(final ActionData data) {
final View dview = LayoutInflater.from(context).inflate(R.layout.dialog_folder_select, null);
final RecyclerView rvFolder = dview.findViewById(R.id.rvFolder);
final ContentLoadingProgressBar pbWait = dview.findViewById(R.id.pbWait);
final Dialog dialog = new DialogBuilderLifecycle(context, owner)
.setTitle(R.string.title_copy_to)
.setView(dview)
.create();
rvFolder.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(context);
rvFolder.setLayoutManager(llm);
final AdapterFolderSelect adapter = new AdapterFolderSelect(context, owner, new AdapterFolderSelect.IFolderSelectedListener() {
@Override
public void onFolderSelected(EntityFolder folder) {
dialog.dismiss();
Bundle args = new Bundle();
args.putLong("id", data.message.id);
args.putLong("target", folder.id);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
long target = args.getLong("target");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityOperation.queue(context, message, EntityOperation.COPY, target);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:copy");
}
});
rvFolder.setAdapter(adapter);
rvFolder.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
dialog.show();
Bundle args = new Bundle();
args.putLong("id", data.message.id);
@ -2471,10 +2531,15 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
if (folders == null)
return null;
if (folders.size() > 0)
Collections.sort(folders, folders.get(0).getComparator(context));
List<EntityFolder> targets = new ArrayList<>();
for (EntityFolder folder : folders)
if (!folder.isHidden(context) && !folder.id.equals(message.folder))
targets.add(folder);
return folders;
if (targets.size() > 0)
Collections.sort(targets, targets.get(0).getComparator(context));
return targets;
}
@Override
@ -2482,53 +2547,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
if (folders == null)
return;
View anchor = bnvActions.findViewById(R.id.action_more);
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor);
int order = 0;
for (EntityFolder folder : folders)
popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, folder.getDisplayName(context));
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem target) {
args.putLong("target", target.getItemId());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
long target = args.getLong("target");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityOperation.queue(context, message, EntityOperation.COPY, target);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:copy");
return true;
}
});
popupMenu.show();
adapter.set(folders);
pbWait.setVisibility(View.GONE);
rvFolder.setVisibility(View.VISIBLE);
}
@Override
@ -3115,7 +3136,33 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
properties.move(data.message.id, EntityFolder.TRASH, true);
}
private void onActionMove(ActionData data) {
private void onActionMove(final ActionData data) {
final View dview = LayoutInflater.from(context).inflate(R.layout.dialog_folder_select, null);
final RecyclerView rvFolder = dview.findViewById(R.id.rvFolder);
final ContentLoadingProgressBar pbWait = dview.findViewById(R.id.pbWait);
final Dialog dialog = new DialogBuilderLifecycle(context, owner)
.setTitle(R.string.title_move_to_folder)
.setView(dview)
.create();
rvFolder.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(context);
rvFolder.setLayoutManager(llm);
final AdapterFolderSelect adapter = new AdapterFolderSelect(context, owner, new AdapterFolderSelect.IFolderSelectedListener() {
@Override
public void onFolderSelected(EntityFolder folder) {
dialog.dismiss();
properties.move(data.message.id, folder.name, false);
}
});
rvFolder.setAdapter(adapter);
rvFolder.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
dialog.show();
Bundle args = new Bundle();
args.putLong("id", data.message.id);
@ -3188,42 +3235,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
if (folders == null)
return;
View anchor = bnvActions.findViewById(R.id.action_move);
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor);
int order = 0;
for (EntityFolder folder : folders)
popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, folder.getDisplayName(context));
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem target) {
args.putLong("target", target.getItemId());
new SimpleTask<String>() {
@Override
protected String onExecute(Context context, Bundle args) {
long target = args.getLong("target");
return DB.getInstance(context).folder().getFolder(target).name;
}
@Override
protected void onExecuted(Bundle args, String folderName) {
long id = args.getLong("id");
properties.move(id, folderName, false);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:move");
return true;
}
});
popupMenu.show();
adapter.set(folders);
pbWait.setVisibility(View.GONE);
rvFolder.setVisibility(View.VISIBLE);
}
@Override

@ -20,6 +20,7 @@ package eu.faircode.email;
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -1396,7 +1397,7 @@ public class FragmentMessages extends FragmentBase {
if (targets.size() > 0)
Collections.sort(targets, targets.get(0).getComparator(context));
result.targets.put(account, targets);
result.targets.put(account.id, targets);
}
return result;
@ -1423,25 +1424,22 @@ public class FragmentMessages extends FragmentBase {
if (result.hasArchive && !result.isArchive) // has archive and not is archive/drafts
popupMenu.getMenu().add(Menu.NONE, R.string.title_archive, 7, R.string.title_archive);
int order = 8;
for (EntityAccount account : result.accounts) {
MenuItem item = popupMenu.getMenu()
.add(Menu.NONE, R.string.title_move_to_account, order++,
getString(R.string.title_move_to_account, account.name));
item.setIntent(new Intent().putExtra("account", account.id));
}
if (result.isTrash) // is trash
popupMenu.getMenu().add(Menu.NONE, R.string.title_delete, 8, R.string.title_delete);
popupMenu.getMenu().add(Menu.NONE, R.string.title_delete, order++, R.string.title_delete);
if (!result.isTrash && result.hasTrash) // not trash and has trash
popupMenu.getMenu().add(Menu.NONE, R.string.title_trash, 9, R.string.title_trash);
popupMenu.getMenu().add(Menu.NONE, R.string.title_trash, order++, R.string.title_trash);
if (result.hasJunk && !result.isJunk && !result.isDrafts) // has junk and not junk/drafts
popupMenu.getMenu().add(Menu.NONE, R.string.title_spam, 10, R.string.title_spam);
int order = 11;
for (EntityAccount account : result.accounts) {
SubMenu smenu = popupMenu.getMenu()
.addSubMenu(Menu.NONE, 0, order++, getString(R.string.title_move_to, account.name));
int sorder = 1;
for (EntityFolder target : result.targets.get(account)) {
MenuItem item = smenu.add(Menu.NONE, R.string.title_move_to, sorder++, target.getDisplayName(getContext()));
item.setIntent(new Intent().putExtra("target", target.id));
}
}
popupMenu.getMenu().add(Menu.NONE, R.string.title_spam, order++, R.string.title_spam);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
@ -1477,8 +1475,9 @@ public class FragmentMessages extends FragmentBase {
case R.string.title_spam:
onActionJunkSelection();
return true;
case R.string.title_move_to:
onActionMoveSelection(target.getIntent().getLongExtra("target", -1));
case R.string.title_move_to_account:
long account = target.getIntent().getLongExtra("account", -1);
onActionMoveSelectionAccount(result.targets.get(account));
return true;
default:
return false;
@ -1829,6 +1828,37 @@ public class FragmentMessages extends FragmentBase {
}.execute(FragmentMessages.this, args, "messages:move");
}
private void onActionMoveSelectionAccount(List<EntityFolder> folders) {
final View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_folder_select, null);
final RecyclerView rvFolder = dview.findViewById(R.id.rvFolder);
final ContentLoadingProgressBar pbWait = dview.findViewById(R.id.pbWait);
final Dialog dialog = new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner())
.setTitle(R.string.title_move_to_folder)
.setView(dview)
.create();
rvFolder.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
rvFolder.setLayoutManager(llm);
final AdapterFolderSelect adapter = new AdapterFolderSelect(getContext(), getViewLifecycleOwner(),
new AdapterFolderSelect.IFolderSelectedListener() {
@Override
public void onFolderSelected(EntityFolder folder) {
dialog.dismiss();
onActionMoveSelection(folder.id);
}
});
adapter.set(folders);
rvFolder.setAdapter(adapter);
rvFolder.setVisibility(View.VISIBLE);
pbWait.setVisibility(View.GONE);
dialog.show();
}
private void onActionMoveSelection(long target) {
Bundle args = new Bundle();
args.putLongArray("ids", getSelection());
@ -3273,7 +3303,7 @@ public class FragmentMessages extends FragmentBase {
Boolean isJunk;
Boolean isDrafts;
List<EntityAccount> accounts;
Map<EntityAccount, List<EntityFolder>> targets = new HashMap<>();
Map<Long, List<EntityFolder>> targets = new HashMap<>();
}
private static class MessageTarget implements Parcelable {

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<eu.faircode.email.ContentLoadingProgressBar
android:id="@+id/pbWait"
style="@style/Base.Widget.AppCompat.ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:padding="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/activatableItemBackground"
android:padding="6dp">
<ImageView
android:id="@+id/ivType"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="@string/title_legend_folder_tye"
android:src="@drawable/baseline_inbox_24"
app:layout_constraintBottom_toBottomOf="@+id/tvName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/tvName" />
<TextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="middle"
android:gravity="center_vertical"
android:minHeight="24dp"
android:singleLine="true"
android:text="Name"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivType"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -404,12 +404,14 @@
<string name="title_trash">Trash</string>
<string name="title_copy">Copy &#8230;</string>
<string name="title_copy_to">Copy to &#8230;</string>
<string name="title_subscribe">Subscribe</string>
<string name="title_delete">Delete</string>
<string name="title_more">More</string>
<string name="title_spam">Spam</string>
<string name="title_move">Move</string>
<string name="title_move_to">Move to %1$s</string>
<string name="title_move_to_folder">Move to &#8230;</string>
<string name="title_move_to_account">Move to %1$s &#8230;</string>
<string name="title_snooze">Snooze &#8230;</string>
<string name="title_archive">Archive</string>
<string name="title_reply">Reply</string>

Loading…
Cancel
Save