From b130da7bc1ae5d925b6da19820a892dae90b8c6a Mon Sep 17 00:00:00 2001 From: M66B Date: Fri, 3 Aug 2018 13:46:25 +0000 Subject: [PATCH] List attachments in message view --- .../eu/faircode/email/AdapterAttachment.java | 182 ++++++++++++++++++ .../java/eu/faircode/email/DaoAttachment.java | 7 + .../eu/faircode/email/FragmentMessage.java | 44 ++++- app/src/main/res/layout/fragment_message.xml | 53 ++++- app/src/main/res/layout/item_attachment.xml | 40 ++++ 5 files changed, 309 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/AdapterAttachment.java create mode 100644 app/src/main/res/layout/item_attachment.xml diff --git a/app/src/main/java/eu/faircode/email/AdapterAttachment.java b/app/src/main/java/eu/faircode/email/AdapterAttachment.java new file mode 100644 index 00000000..c07bc2fa --- /dev/null +++ b/app/src/main/java/eu/faircode/email/AdapterAttachment.java @@ -0,0 +1,182 @@ +package eu.faircode.email; + +/* + This file is part of Safe email. + + Safe email 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. + + NetGuard 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 NetGuard. If not, see . + + Copyright 2018 by Marcel Bokhorst (M66B) +*/ + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.util.DiffUtil; +import android.support.v7.util.ListUpdateCallback; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class AdapterAttachment extends RecyclerView.Adapter { + private Context context; + + private List all = new ArrayList<>(); + private List filtered = new ArrayList<>(); + + public class ViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener { + View itemView; + TextView tvName; + TextView tvType; + + ViewHolder(View itemView) { + super(itemView); + + this.itemView = itemView; + tvName = itemView.findViewById(R.id.tvName); + tvType = itemView.findViewById(R.id.tvType); + } + + private void wire() { + itemView.setOnClickListener(this); + } + + private void unwire() { + itemView.setOnClickListener(null); + } + + @Override + public void onClick(View view) { + EntityAttachment attachment = filtered.get(getLayoutPosition()); + if (attachment.content == null) { + + } + } + } + + AdapterAttachment(Context context) { + this.context = context; + setHasStableIds(true); + } + + public void set(List attachments) { + Log.i(Helper.TAG, "Set attachments=" + attachments.size()); + + Collections.sort(attachments, new Comparator() { + @Override + public int compare(EntityAttachment a1, EntityAttachment a2) { + return a1.sequence.compareTo(a2.sequence); + } + }); + + all.clear(); + all.addAll(attachments); + + DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all)); + + filtered.clear(); + filtered.addAll(all); + + diff.dispatchUpdatesTo(new ListUpdateCallback() { + @Override + public void onInserted(int position, int count) { + Log.i(Helper.TAG, "Inserted @" + position + " #" + count); + } + + @Override + public void onRemoved(int position, int count) { + Log.i(Helper.TAG, "Removed @" + position + " #" + count); + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition); + } + + @Override + public void onChanged(int position, int count, Object payload) { + Log.i(Helper.TAG, "Changed @" + position + " #" + count); + } + }); + diff.dispatchUpdatesTo(AdapterAttachment.this); + } + + private class MessageDiffCallback extends DiffUtil.Callback { + private List prev; + private List next; + + MessageDiffCallback(List prev, List next) { + this.prev = prev; + this.next = next; + } + + @Override + public int getOldListSize() { + return prev.size(); + } + + @Override + public int getNewListSize() { + return next.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + EntityAttachment a1 = prev.get(oldItemPosition); + EntityAttachment a2 = next.get(newItemPosition); + return a1.id.equals(a2.id); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + EntityAttachment a1 = prev.get(oldItemPosition); + EntityAttachment a2 = next.get(newItemPosition); + return a1.equals(a2); + } + } + + @Override + public long getItemId(int position) { + return filtered.get(position).id; + } + + @Override + public int getItemCount() { + return filtered.size(); + } + + @Override + @NonNull + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_attachment, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.unwire(); + + EntityAttachment attachment = filtered.get(position); + holder.tvName.setText(attachment.name); + holder.tvType.setText(attachment.type); + + holder.wire(); + } +} diff --git a/app/src/main/java/eu/faircode/email/DaoAttachment.java b/app/src/main/java/eu/faircode/email/DaoAttachment.java index d27781cb..534471cc 100644 --- a/app/src/main/java/eu/faircode/email/DaoAttachment.java +++ b/app/src/main/java/eu/faircode/email/DaoAttachment.java @@ -19,12 +19,19 @@ package eu.faircode.email; Copyright 2018 by Marcel Bokhorst (M66B) */ +import android.arch.lifecycle.LiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import java.util.List; @Dao public interface DaoAttachment { + @Query("SELECT * FROM attachment WHERE message = :message") + LiveData> liveAttachments(long message); + @Insert(onConflict = OnConflictStrategy.REPLACE) long insertAttachment(EntityAttachment attachment); } diff --git a/app/src/main/java/eu/faircode/email/FragmentMessage.java b/app/src/main/java/eu/faircode/email/FragmentMessage.java index 215b8995..e7d2d5af 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessage.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessage.java @@ -33,6 +33,8 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.text.Html; import android.text.Layout; import android.text.Spannable; @@ -53,6 +55,7 @@ import android.widget.Toast; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -62,15 +65,18 @@ public class FragmentMessage extends Fragment { private TextView tvTo; private TextView tvCc; private TextView tvBcc; + private RecyclerView rvAttachment; private TextView tvSubject; private TextView tvCount; private BottomNavigationView top_navigation; private TextView tvBody; private BottomNavigationView bottom_navigation; private ProgressBar pbWait; - private Group grpCc; + private Group grpAddress; + private Group grpAttachments; private Group grpReady; + private AdapterAttachment adapter; private LiveData liveFolder; private ExecutorService executor = Executors.newCachedThreadPool(); @@ -91,6 +97,7 @@ public class FragmentMessage extends Fragment { tvTo = view.findViewById(R.id.tvTo); tvCc = view.findViewById(R.id.tvCc); tvBcc = view.findViewById(R.id.tvBcc); + rvAttachment = view.findViewById(R.id.rvAttachment); tvTime = view.findViewById(R.id.tvTime); tvSubject = view.findViewById(R.id.tvSubject); tvCount = view.findViewById(R.id.tvCount); @@ -98,12 +105,13 @@ public class FragmentMessage extends Fragment { tvBody = view.findViewById(R.id.tvBody); bottom_navigation = view.findViewById(R.id.bottom_navigation); pbWait = view.findViewById(R.id.pbWait); - grpCc = view.findViewById(R.id.grpCc); + grpAddress = view.findViewById(R.id.grpAddress); + grpAttachments = view.findViewById(R.id.grpAttachments); grpReady = view.findViewById(R.id.grpReady); setHasOptionsMenu(true); - tvBody.setMovementMethod(new LinkMovementMethod() { + tvBody.setMovementMethod(new LinkMovementMethod() { public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { if (event.getAction() != MotionEvent.ACTION_UP) return super.onTouchEvent(widget, buffer, event); @@ -130,7 +138,7 @@ public class FragmentMessage extends Fragment { fragment.setArguments(args); FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); - fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("link"); + fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("webview"); fragmentTransaction.commit(); } return true; @@ -185,11 +193,19 @@ public class FragmentMessage extends Fragment { }); // Initialize - grpCc.setVisibility(View.GONE); + grpAddress.setVisibility(View.GONE); + grpAttachments.setVisibility(View.GONE); grpReady.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); - DB db = DB.getInstance(getContext()); + rvAttachment.setHasFixedSize(false); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + rvAttachment.setLayoutManager(llm); + + adapter = new AdapterAttachment(getContext()); + rvAttachment.setAdapter(adapter); + + final DB db = DB.getInstance(getContext()); // Observe folder liveFolder = db.folder().liveFolderEx(folder); @@ -213,7 +229,6 @@ public class FragmentMessage extends Fragment { tvTime.setText(message.sent == null ? null : df.format(new Date(message.sent))); tvSubject.setText(message.subject); tvCount.setText(Integer.toString(message.count)); - tvCount.setVisibility(message.count > 1 ? View.VISIBLE : View.GONE); int visibility = (message.ui_seen ? Typeface.NORMAL : Typeface.BOLD); tvFrom.setTypeface(null, visibility); @@ -221,6 +236,10 @@ public class FragmentMessage extends Fragment { tvSubject.setTypeface(null, visibility); tvCount.setTypeface(null, visibility); + // Observe attachments + db.attachment().liveAttachments(id).removeObservers(FragmentMessage.this); + db.attachment().liveAttachments(id).observe(FragmentMessage.this, attachmentsObserver); + MenuItem actionSeen = top_navigation.getMenu().findItem(R.id.action_seen); actionSeen.setIcon(message.ui_seen ? R.drawable.baseline_visibility_off_24 @@ -269,7 +288,8 @@ public class FragmentMessage extends Fragment { } private void onMenuCc() { - grpCc.setVisibility(grpCc.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); + if (grpReady.getVisibility() == View.VISIBLE) + grpAddress.setVisibility(grpAddress.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); } Observer folderObserver = new Observer() { @@ -281,6 +301,14 @@ public class FragmentMessage extends Fragment { } }; + Observer> attachmentsObserver = new Observer>() { + @Override + public void onChanged(@Nullable List attachments) { + adapter.set(attachments); + grpAttachments.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE); + } + }; + private void onActionSeen(final long id) { executor.submit(new Runnable() { @Override diff --git a/app/src/main/res/layout/fragment_message.xml b/app/src/main/res/layout/fragment_message.xml index cc4c6afa..176f3fe9 100644 --- a/app/src/main/res/layout/fragment_message.xml +++ b/app/src/main/res/layout/fragment_message.xml @@ -13,6 +13,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="6dp" android:layout_marginStart="6dp" + android:layout_marginTop="3dp" android:text="From" android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textIsSelectable="true" @@ -57,10 +58,10 @@ app:layout_constraintTop_toTopOf="@id/tvSubject" /> @@ -70,11 +71,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="6dp" + android:layout_marginTop="3dp" android:maxLines="1" android:text="@string/title_to" android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/vSeparator" /> + app:layout_constraintTop_toBottomOf="@id/vSeparatorAddress" /> + app:layout_constraintTop_toBottomOf="@id/vSeparatorAddress" /> + app:layout_constraintTop_toBottomOf="@id/tvTo" /> + app:layout_constraintTop_toBottomOf="@id/tvCc" /> + + + + + + + app:constraint_referenced_ids="vSeparatorAttachments,rvAttachment" /> + + + + + + + + \ No newline at end of file