about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorFoysal Ahamed <foysal@blueskyweb.xyz>2023-07-28 18:04:27 +0200
committerGitHub <noreply@github.com>2023-07-28 11:04:27 -0500
commiteec300d77241925e6b42e5e7e51894f2cba50e18 (patch)
tree0fb74f898687a6331cba3a3f0cffe3354596f4ab /src
parent38d78e16bffc9a25a45a4ad41caeef2c075daa26 (diff)
downloadvoidsky-eec300d77241925e6b42e5e7e51894f2cba50e18.tar.zst
List cleanup on remove (#1069)
* :lipstick: Hide Add to List option on own profile

* :sparkles: Remove Lists tab when last list is removed

* :sparkles: Add listener to list delete on profile screen

* :sparkles: Only show save changes in list modal when changes are made
Diffstat (limited to 'src')
-rw-r--r--src/state/models/content/list.ts2
-rw-r--r--src/state/models/lists/lists-list.ts15
-rw-r--r--src/state/models/root-store.ts8
-rw-r--r--src/state/models/ui/profile.ts5
-rw-r--r--src/view/com/modals/ListAddRemoveUser.tsx34
-rw-r--r--src/view/screens/Profile.tsx8
6 files changed, 60 insertions, 12 deletions
diff --git a/src/state/models/content/list.ts b/src/state/models/content/list.ts
index c5ac72e49..2498cf581 100644
--- a/src/state/models/content/list.ts
+++ b/src/state/models/content/list.ts
@@ -217,6 +217,8 @@ export class ListModel {
         records.map(record => createDel(record.uri)),
       ),
     })
+
+    this.rootStore.emitListDeleted(this.uri)
   }
 
   async subscribe() {
diff --git a/src/state/models/lists/lists-list.ts b/src/state/models/lists/lists-list.ts
index 6618c3bf6..54e2f5fde 100644
--- a/src/state/models/lists/lists-list.ts
+++ b/src/state/models/lists/lists-list.ts
@@ -48,9 +48,24 @@ export class ListsListModel {
     return this.hasLoaded && !this.hasContent
   }
 
+  /**
+   * Removes posts from the feed upon deletion.
+   */
+  onListDeleted(uri: string) {
+    this.lists = this.lists.filter(l => l.uri !== uri)
+  }
+
   // public api
   // =
 
+  /**
+   * Register any event listeners. Returns a cleanup function.
+   */
+  registerListeners() {
+    const sub = this.rootStore.onListDeleted(this.onListDeleted.bind(this))
+    return () => sub.remove()
+  }
+
   async refresh() {
     return this.loadMore(true)
   }
diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts
index 389ce86d8..d76ea07c9 100644
--- a/src/state/models/root-store.ts
+++ b/src/state/models/root-store.ts
@@ -188,6 +188,14 @@ export class RootStoreModel {
     DeviceEventEmitter.emit('post-deleted', uri)
   }
 
+  // a list was deleted by the local user
+  onListDeleted(handler: (uri: string) => void): EmitterSubscription {
+    return DeviceEventEmitter.addListener('list-deleted', handler)
+  }
+  emitListDeleted(uri: string) {
+    DeviceEventEmitter.emit('list-deleted', uri)
+  }
+
   // the session has started and been fully hydrated
   onSessionLoaded(handler: () => void): EmitterSubscription {
     return DeviceEventEmitter.addListener('session-loaded', handler)
diff --git a/src/state/models/ui/profile.ts b/src/state/models/ui/profile.ts
index 81daf797f..a0249d768 100644
--- a/src/state/models/ui/profile.ts
+++ b/src/state/models/ui/profile.ts
@@ -87,7 +87,10 @@ export class ProfileUiModel {
   }
 
   get selectedView() {
-    return this.selectorItems[this.selectedViewIndex]
+    // If, for whatever reason, the selected view index is not available, default back to posts
+    // This can happen when the user was focused on a view but performed an action that caused
+    // the view to disappear (e.g. deleting the last list in their list of lists https://imgflip.com/i/7txu1y)
+    return this.selectorItems[this.selectedViewIndex] || Sections.Posts
   }
 
   get uiItems() {
diff --git a/src/view/com/modals/ListAddRemoveUser.tsx b/src/view/com/modals/ListAddRemoveUser.tsx
index 49f46e741..0f001f911 100644
--- a/src/view/com/modals/ListAddRemoveUser.tsx
+++ b/src/view/com/modals/ListAddRemoveUser.tsx
@@ -20,6 +20,7 @@ import {sanitizeHandle} from 'lib/strings/handles'
 import {s} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {isDesktopWeb, isAndroid} from 'platform/detection'
+import isEqual from 'lodash.isequal'
 
 export const snapPoints = ['fullscreen']
 
@@ -37,6 +38,9 @@ export const Component = observer(
     const pal = usePalette('default')
     const palPrimary = usePalette('primary')
     const palInverted = usePalette('inverted')
+    const [originalSelections, setOriginalSelections] = React.useState<
+      string[]
+    >([])
     const [selected, setSelected] = React.useState<string[]>([])
 
     const listsList: ListsListModel = React.useMemo(
@@ -51,7 +55,9 @@ export const Component = observer(
       listsList.refresh()
       memberships.fetch().then(
         () => {
-          setSelected(memberships.memberships.map(m => m.value.list))
+          const ids = memberships.memberships.map(m => m.value.list)
+          setOriginalSelections(ids)
+          setSelected(ids)
         },
         err => {
           store.log.error('Failed to fetch memberships', {err})
@@ -156,6 +162,10 @@ export const Component = observer(
       )
     }, [onPressNewMuteList])
 
+    // Only show changes button if there are some items on the list to choose from AND user has made changes in selection
+    const canSaveChanges =
+      !listsList.isEmpty && !isEqual(selected, originalSelections)
+
     return (
       <View testID="listAddRemoveUserModal" style={s.hContentRegion}>
         <Text style={[styles.title, pal.text]}>Add {displayName} to Lists</Text>
@@ -178,16 +188,18 @@ export const Component = observer(
             onAccessibilityEscape={onPressCancel}
             label="Cancel"
           />
-          <Button
-            testID="saveBtn"
-            type="primary"
-            onPress={onPressSave}
-            style={styles.footerBtn}
-            accessibilityLabel="Save changes"
-            accessibilityHint=""
-            onAccessibilityEscape={onPressSave}
-            label="Save Changes"
-          />
+          {canSaveChanges && (
+            <Button
+              testID="saveBtn"
+              type="primary"
+              onPress={onPressSave}
+              style={styles.footerBtn}
+              accessibilityLabel="Save changes"
+              accessibilityHint=""
+              onAccessibilityEscape={onPressSave}
+              label="Save Changes"
+            />
+          )}
         </View>
       </View>
     )
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 390266440..f00585336 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -56,6 +56,13 @@ export const ProfileScreen = withAuthRequired(
       setHasSetup(false)
     }, [route.params.name])
 
+    // We don't need this to be reactive, so we can just register the listeners once
+    useEffect(() => {
+      const listCleanup = uiState.lists.registerListeners()
+      return () => listCleanup()
+      // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [])
+
     useFocusEffect(
       React.useCallback(() => {
         const softResetSub = store.onScreenSoftReset(onSoftReset)
@@ -126,6 +133,7 @@ export const ProfileScreen = withAuthRequired(
         />
       )
     }, [uiState, onRefresh, route.params.hideBackButton])
+
     const Footer = React.useMemo(() => {
       return uiState.showLoadingMoreFooter ? LoadingMoreFooter : undefined
     }, [uiState.showLoadingMoreFooter])