From e3696e3ac5b3c0a872dca764f718ad0eafc15f62 Mon Sep 17 00:00:00 2001
From: Trent Larson <trent@trentlarson.com>
Date: Wed, 3 Apr 2024 19:54:01 -0600
Subject: [PATCH] feed filter: save the changed values to the DB, go to map if
 no location chosen, reload if necessary

---
 project.task.yaml              |   1 +
 src/components/FeedFilters.vue | 115 +++++++++++++++++++++++++++------
 src/db/tables/settings.ts      |   4 ++
 src/views/HomeView.vue         |  28 ++++++--
 src/views/SearchAreaView.vue   |   5 +-
 5 files changed, 129 insertions(+), 24 deletions(-)

diff --git a/project.task.yaml b/project.task.yaml
index 5a8b1cc..d111576 100644
--- a/project.task.yaml
+++ b/project.task.yaml
@@ -19,6 +19,7 @@ tasks :
 - .2 don't show a warning on a totally new project when the authorized agent is set
 - .2 anchor hash into BTC
 - .2 list the "show more" contacts alphabetically
+- .5 add back the explicit wait for browser subscription timing problems?
 
 - .5 make Time Safari a share_target for images
 
diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue
index 390205b..669e953 100644
--- a/src/components/FeedFilters.vue
+++ b/src/components/FeedFilters.vue
@@ -6,7 +6,10 @@
       <p class="mb-4 font-bold">Show only activities that are…</p>
 
       <div class="grid grid-cols-1 gap-2">
-        <div class="flex items-center justify-between cursor-pointer">
+        <div
+          class="flex items-center justify-between cursor-pointer"
+          @click="toggleContacts()"
+        >
           <!-- label -->
           <div>From my contacts</div>
           <!-- toggle -->
@@ -14,7 +17,7 @@
             <!-- input -->
             <input
               type="checkbox"
-              v-model="isFromMyContacts"
+              v-model="isInMyContacts"
               name="toggleFilterFromMyContacts"
               class="sr-only"
             />
@@ -27,11 +30,18 @@
           </div>
         </div>
 
-        <div class="flex items-center justify-between cursor-pointer">
+        <div
+          class="flex items-center justify-between cursor-pointer"
+          @click="
+            hasSearchBox
+              ? toggleNearby()
+              : $router.push({ name: 'search-area' })
+          "
+        >
           <!-- label -->
           <div>Nearby</div>
           <!-- toggle -->
-          <div class="relative ml-2">
+          <div v-if="hasSearchBox" class="relative ml-2">
             <!-- input -->
             <input
               type="checkbox"
@@ -46,26 +56,32 @@
               class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
             ></div>
           </div>
+          <div v-else class="relative ml-2">
+            <button class="ml-2 px-4 py-2 rounded-md bg-blue-200 text-blue-500">
+              Select Location
+            </button>
+          </div>
         </div>
       </div>
 
-      <button
-        class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mt-4"
-        @click="confirm"
-      >
-        Apply
-      </button>
-      <div class="grid grid-cols-1 sm:grid-cols-2 gap-2 mt-2">
+      <div class="grid grid-cols-1 sm:grid-cols-3 gap-2 mt-4">
         <button
           class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
+          @click="setAll()"
+        >
+          Set All
+        </button>
+        <button
+          class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
+          @click="clearAll()"
         >
           Clear All
         </button>
         <button
           class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
-          @click="cancel"
+          @click="done()"
         >
-          Cancel
+          Done
         </button>
       </div>
     </div>
@@ -81,6 +97,9 @@ import {
   LTileLayer,
 } from "@vue-leaflet/vue-leaflet";
 
+import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
+import { db } from "@/db/index";
+
 @Component({
   components: {
     LRectangle,
@@ -90,20 +109,80 @@ import {
   },
 })
 export default class FeedFilters extends Vue {
-  visible = false;
-  isFromMyContacts = false;
+  callOnCloseIfChanged = () => {};
+  hasSearchBox = false;
+  isInMyContacts = false;
   isNearby = false;
+  settingChanged = false;
+  visible = false;
+
+  async open(callOnCloseIfChanged: () => void) {
+    this.callOnCloseIfChanged = callOnCloseIfChanged;
+
+    await db.open();
+    const settings = await db.settings.get(MASTER_SETTINGS_KEY);
+    this.isInMyContacts = !!settings?.filterFeedContacts;
+    this.isNearby = !!settings?.filterFeedNearby;
+    if (settings?.searchBoxes && settings.searchBoxes.length > 0) {
+      this.hasSearchBox = true;
+    }
 
-  async open() {
+    this.settingChanged = false;
     this.visible = true;
   }
 
+  toggleContacts() {
+    this.settingChanged = true;
+    this.isInMyContacts = !this.isInMyContacts;
+    db.settings.update(MASTER_SETTINGS_KEY, {
+      filterFeedContacts: this.isInMyContacts,
+    });
+  }
+
+  toggleNearby() {
+    this.settingChanged = true;
+    this.isNearby = !this.isNearby;
+    db.settings.update(MASTER_SETTINGS_KEY, {
+      filterFeedNearby: this.isNearby,
+    });
+  }
+
+  async clearAll() {
+    if (this.isInMyContacts || this.isNearby) {
+      this.settingChanged = true;
+    }
+
+    db.settings.update(MASTER_SETTINGS_KEY, {
+      filterFeedNearby: false,
+      filterFeedContacts: false,
+    });
+
+    this.isInMyContacts = false;
+    this.isNearby = false;
+  }
+
+  async setAll() {
+    if (!this.isInMyContacts || !this.isNearby) {
+      this.settingChanged = true;
+    }
+
+    db.settings.update(MASTER_SETTINGS_KEY, {
+      filterFeedNearby: true,
+      filterFeedContacts: true,
+    });
+
+    this.isInMyContacts = true;
+    this.isNearby = true;
+  }
+
   close() {
-    // close the dialog but don't change values (just in case some actions are added later)
+    if (this.settingChanged) {
+      this.callOnCloseIfChanged();
+    }
     this.visible = false;
   }
 
-  cancel() {
+  done() {
     this.close();
   }
 }
diff --git a/src/db/tables/settings.ts b/src/db/tables/settings.ts
index f95b383..479ee63 100644
--- a/src/db/tables/settings.ts
+++ b/src/db/tables/settings.ts
@@ -16,6 +16,10 @@ export type Settings = {
 
   activeDid?: string; // Active Decentralized ID
   apiServer?: string; // API server URL
+
+  filterFeedNearby?: string; // filter by nearby
+  filterFeedContacts?: string; // filter by user contacts
+
   firstName?: string; // User's first name
   isRegistered?: boolean;
   lastName?: string; // deprecated - put all names in firstName
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index e6172ce..4194151 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -180,9 +180,9 @@
         <h2 class="text-xl font-bold">Latest Activity</h2>
         <button
           @click="openFeedFilters()"
-          class="block text-center text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 ml-auto rounded-md"
+          class="block text-center text-sm uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 ml-auto rounded-md"
         >
-          Filter…
+          {{ resultsAreFiltered() ? "Filtered" : "Unfiltered" }}
         </button>
       </div>
       <InfiniteScroll @reached-bottom="loadMoreGives">
@@ -311,6 +311,8 @@ export default class HomeView extends Vue {
   feedPreviousOldestId?: string;
   feedLastViewedClaimId?: string;
   isCreatingIdentifier = false;
+  isFeedFilteredByContacts = false;
+  isFeedFilteredByNearby = false;
   isFeedLoading = true;
   isRegistered = false;
   showShortcutBvc = false;
@@ -335,7 +337,7 @@ export default class HomeView extends Vue {
     return headers;
   }
 
-  async created() {
+  async mounted() {
     try {
       await accountsDB.open();
       const allAccounts = await accountsDB.accounts.toArray();
@@ -347,6 +349,8 @@ export default class HomeView extends Vue {
       this.activeDid = settings?.activeDid || "";
       this.allContacts = await db.contacts.toArray();
       this.feedLastViewedClaimId = settings?.lastViewedClaimId;
+      this.isFeedFilteredByContacts = !!settings?.filterFeedContacts;
+      this.isFeedFilteredByNearby = !!settings?.filterFeedNearby;
       this.isRegistered = !!settings?.isRegistered;
       this.showShortcutBvc = !!settings?.showShortcutBvc;
 
@@ -378,6 +382,10 @@ export default class HomeView extends Vue {
     }
   }
 
+  resultsAreFiltered() {
+    return this.isFeedFilteredByContacts || this.isFeedFilteredByNearby;
+  }
+
   notificationsSupported() {
     return "Notification" in window;
   }
@@ -408,6 +416,18 @@ export default class HomeView extends Vue {
     return headers;
   }
 
+  // only called when a setting was changed
+  async reloadFeedOnChange() {
+    await db.open();
+    const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
+    this.isFeedFilteredByContacts = !!settings?.filterFeedContacts;
+    this.isFeedFilteredByNearby = !!settings?.filterFeedNearby;
+
+    this.feedData = [];
+    this.feedPreviousOldestId = undefined;
+    this.updateAllFeed();
+  }
+
   /**
    * Data loader used by infinite scroller
    * @param payload is the flag from the InfiniteScroll indicating if it should load
@@ -587,7 +607,7 @@ export default class HomeView extends Vue {
   }
 
   openFeedFilters() {
-    (this.$refs.feedFilters as FeedFilters).open();
+    (this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
   }
 }
 </script>
diff --git a/src/views/SearchAreaView.vue b/src/views/SearchAreaView.vue
index 4b3a750..e9c30e8 100644
--- a/src/views/SearchAreaView.vue
+++ b/src/views/SearchAreaView.vue
@@ -208,9 +208,9 @@ export default class DiscoverView extends Vue {
             group: "alert",
             type: "success",
             title: "Saved",
-            text: "That has been saved in your preferences.",
+            text: "That has been saved in your preferences. You can now filter by it on your home screen feed.",
           },
-          -1,
+          7000,
         );
         this.$router.back();
       } catch (err) {
@@ -246,6 +246,7 @@ export default class DiscoverView extends Vue {
       await db.open();
       db.settings.update(MASTER_SETTINGS_KEY, {
         searchBoxes: [],
+        filterFeedNearby: false,
       });
       this.searchBox = null;
       this.localCenterLat = 0;