Allow storing all media in cache on a per-chat basis

Stephen Paul Weber created

Change summary

src/main/java/eu/siacs/conversations/entities/Conversation.java        | 11 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java      |  3 
src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java |  7 
src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java    |  7 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java      |  9 
src/main/res/layout/activity_contact_details.xml                       | 23 
src/main/res/layout/activity_muc_details.xml                           | 23 
7 files changed, 77 insertions(+), 6 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -1148,6 +1148,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return alwaysNotify() || getBooleanAttribute(ATTRIBUTE_NOTIFY_REPLIES, false);
     }
 
+    public void setStoreInCache(final boolean cache) {
+        setAttribute("storeMedia", cache ? "cache" : "shared");
+    }
+
+    public boolean storeInCache() {
+        if ("cache".equals(getAttribute("storeMedia"))) return true;
+        if ("shared".equals(getAttribute("storeMedia"))) return false;
+        if (mode == Conversation.MODE_MULTI && !mucOptions.isPrivateAndNonAnonymous()) return true;
+        return false;
+    }
+
     public boolean setAttribute(String key, boolean value) {
         return setAttribute(key, String.valueOf(value));
     }

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -82,6 +82,7 @@ import io.ipfs.cid.Cid;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.services.AttachFileToConversationRunnable;
@@ -1083,7 +1084,7 @@ public class FileBackend {
         }
         final File appDirectory =
                 new File(parentDirectory, mXmppConnectionService.getString(R.string.app_name));
-        if (message == null || message.getStatus() == Message.STATUS_DUMMY) {
+        if (message == null || message.getStatus() == Message.STATUS_DUMMY || (message.getConversation() instanceof Conversation && ((Conversation) message.getConversation()).storeInCache())) {
             final var mediaCache = new File(mXmppConnectionService.getCacheDir(), "/media");
             return new File(mediaCache, filename);
         } else {

src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java 🔗

@@ -518,6 +518,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
                     xmppConnectionService.getAttachments(this.mConversation, limit, this);
                     this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation));
                 }
+
+                binding.storeInCache.setChecked(mConversation.storeInCache());
+                binding.storeInCache.setOnCheckedChangeListener((v, checked) -> {
+                    mConversation.setStoreInCache(checked);
+                    xmppConnectionService.updateConversation(mConversation);
+                });
+
                 updateView();
             }
         }

src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java 🔗

@@ -735,6 +735,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                 });
             });
 
+            final var conversation = xmppConnectionService.findOrCreateConversation(account, contact.getJid(), false, true);
+            binding.storeInCache.setChecked(conversation.storeInCache());
+            binding.storeInCache.setOnCheckedChangeListener((v, checked) -> {
+                conversation.setStoreInCache(checked);
+                xmppConnectionService.updateConversation(conversation);
+            });
+
             populateView();
         }
     }

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -1787,12 +1787,11 @@ public class ConversationFragment extends XmppFragment
             }
             if (m.isFileOrImage() && !deleted && !cancelable) {
                 final String path = m.getRelativeFilePath();
-                if (path == null
-                        || !path.startsWith("/")
-                        || FileBackend.inConversationsDirectory(requireActivity(), path)) {
-                    saveAsSticker.setVisible(true);
+                if (path != null) {
+                    final var file = new File(path);
+                    if (file.canRead()) saveAsSticker.setVisible(true);
                     blockMedia.setVisible(true);
-                    deleteFile.setVisible(true);
+                    if (file.canWrite()) deleteFile.setVisible(true);
                     deleteFile.setTitle(
                             activity.getString(
                                     R.string.delete_x_file,

src/main/res/layout/activity_contact_details.xml 🔗

@@ -180,6 +180,29 @@
                         android:orientation="vertical"
                         android:padding="@dimen/card_padding_regular">
 
+                        <RelativeLayout
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+
+                            <TextView
+                                android:text="Store media only in cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentStart="true"
+                                android:layout_centerVertical="true"
+                                android:singleLine="true"
+                                android:textAppearance="?textAppearanceBodyMedium" />
+
+                            <androidx.appcompat.widget.SwitchCompat
+                                android:id="@+id/store_in_cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentEnd="true"
+                                android:layout_centerVertical="true"
+                                android:focusable="false" />
+
+                        </RelativeLayout>
+
                         <androidx.recyclerview.widget.RecyclerView
                             android:id="@+id/media"
                             android:layout_width="match_parent"

src/main/res/layout/activity_muc_details.xml 🔗

@@ -431,6 +431,29 @@
                         android:orientation="vertical"
                         android:padding="@dimen/card_padding_regular">
 
+                        <RelativeLayout
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+
+                            <TextView
+                                android:text="Store media only in cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentStart="true"
+                                android:layout_centerVertical="true"
+                                android:singleLine="true"
+                                android:textAppearance="?textAppearanceBodyMedium" />
+
+                            <androidx.appcompat.widget.SwitchCompat
+                                android:id="@+id/store_in_cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentEnd="true"
+                                android:layout_centerVertical="true"
+                                android:focusable="false" />
+
+                        </RelativeLayout>
+
                         <androidx.recyclerview.widget.RecyclerView
                             android:id="@+id/media"
                             android:layout_width="match_parent"