List cleanup on remove (#1069)

* 💄 Hide Add to List option on own profile

*  Remove Lists tab when last list is removed

*  Add listener to list delete on profile screen

*  Only show save changes in list modal when changes are made
zio/stable
Foysal Ahamed 2023-07-28 18:04:27 +02:00 committed by GitHub
parent 38d78e16bf
commit eec300d772
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 12 deletions

View File

@ -217,6 +217,8 @@ export class ListModel {
records.map(record => createDel(record.uri)), records.map(record => createDel(record.uri)),
), ),
}) })
this.rootStore.emitListDeleted(this.uri)
} }
async subscribe() { async subscribe() {

View File

@ -48,9 +48,24 @@ export class ListsListModel {
return this.hasLoaded && !this.hasContent 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 // 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() { async refresh() {
return this.loadMore(true) return this.loadMore(true)
} }

View File

@ -188,6 +188,14 @@ export class RootStoreModel {
DeviceEventEmitter.emit('post-deleted', uri) 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 // the session has started and been fully hydrated
onSessionLoaded(handler: () => void): EmitterSubscription { onSessionLoaded(handler: () => void): EmitterSubscription {
return DeviceEventEmitter.addListener('session-loaded', handler) return DeviceEventEmitter.addListener('session-loaded', handler)

View File

@ -87,7 +87,10 @@ export class ProfileUiModel {
} }
get selectedView() { 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() { get uiItems() {

View File

@ -20,6 +20,7 @@ import {sanitizeHandle} from 'lib/strings/handles'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb, isAndroid} from 'platform/detection' import {isDesktopWeb, isAndroid} from 'platform/detection'
import isEqual from 'lodash.isequal'
export const snapPoints = ['fullscreen'] export const snapPoints = ['fullscreen']
@ -37,6 +38,9 @@ export const Component = observer(
const pal = usePalette('default') const pal = usePalette('default')
const palPrimary = usePalette('primary') const palPrimary = usePalette('primary')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const [originalSelections, setOriginalSelections] = React.useState<
string[]
>([])
const [selected, setSelected] = React.useState<string[]>([]) const [selected, setSelected] = React.useState<string[]>([])
const listsList: ListsListModel = React.useMemo( const listsList: ListsListModel = React.useMemo(
@ -51,7 +55,9 @@ export const Component = observer(
listsList.refresh() listsList.refresh()
memberships.fetch().then( 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 => { err => {
store.log.error('Failed to fetch memberships', {err}) store.log.error('Failed to fetch memberships', {err})
@ -156,6 +162,10 @@ export const Component = observer(
) )
}, [onPressNewMuteList]) }, [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 ( return (
<View testID="listAddRemoveUserModal" style={s.hContentRegion}> <View testID="listAddRemoveUserModal" style={s.hContentRegion}>
<Text style={[styles.title, pal.text]}>Add {displayName} to Lists</Text> <Text style={[styles.title, pal.text]}>Add {displayName} to Lists</Text>
@ -178,16 +188,18 @@ export const Component = observer(
onAccessibilityEscape={onPressCancel} onAccessibilityEscape={onPressCancel}
label="Cancel" label="Cancel"
/> />
<Button {canSaveChanges && (
testID="saveBtn" <Button
type="primary" testID="saveBtn"
onPress={onPressSave} type="primary"
style={styles.footerBtn} onPress={onPressSave}
accessibilityLabel="Save changes" style={styles.footerBtn}
accessibilityHint="" accessibilityLabel="Save changes"
onAccessibilityEscape={onPressSave} accessibilityHint=""
label="Save Changes" onAccessibilityEscape={onPressSave}
/> label="Save Changes"
/>
)}
</View> </View>
</View> </View>
) )

View File

@ -56,6 +56,13 @@ export const ProfileScreen = withAuthRequired(
setHasSetup(false) setHasSetup(false)
}, [route.params.name]) }, [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( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
const softResetSub = store.onScreenSoftReset(onSoftReset) const softResetSub = store.onScreenSoftReset(onSoftReset)
@ -126,6 +133,7 @@ export const ProfileScreen = withAuthRequired(
/> />
) )
}, [uiState, onRefresh, route.params.hideBackButton]) }, [uiState, onRefresh, route.params.hideBackButton])
const Footer = React.useMemo(() => { const Footer = React.useMemo(() => {
return uiState.showLoadingMoreFooter ? LoadingMoreFooter : undefined return uiState.showLoadingMoreFooter ? LoadingMoreFooter : undefined
}, [uiState.showLoadingMoreFooter]) }, [uiState.showLoadingMoreFooter])