Implement mutelist creation

This commit is contained in:
Paul Frazee
2023-05-05 15:50:31 -05:00
parent 0038a77f37
commit 72340aa395
5 changed files with 187 additions and 17 deletions
+151
View File
@@ -0,0 +1,151 @@
import {makeAutoObservable} from 'mobx'
import {
AppBskyGraphGetList as GetList,
AppBskyActorDefs,
AppBskyGraphDefs,
AppBskyGraphList,
AppBskyRichtextFacet,
RichText,
} from '@atproto/api'
import {Image as RNImage} from 'react-native-image-crop-picker'
import {RootStoreModel} from '../root-store'
import * as apilib from 'lib/api/index'
import {cleanError} from 'lib/strings/errors'
export class ListModel {
// state
isLoading = false
isRefreshing = false
hasLoaded = false
error = ''
params: GetList.QueryParams
// data
uri: string
creator: AppBskyActorDefs.ProfileView
name: string
purpose: AppBskyGraphDefs.ListPurpose
description?: string
descriptionFacets?: AppBskyRichtextFacet.Main[]
avatar?: string
viewer?: AppBskyGraphDefs.ListViewerState
indexedAt?: string
// added data
descriptionRichText?: RichText = new RichText({text: ''})
static async createModList(
rootStore: RootStoreModel,
{
name,
description,
avatar,
}: {name: string; description: string; avatar: RNImage | undefined},
) {
const record: AppBskyGraphList.Record = {
purpose: 'app.bsky.graph.defs#modlist',
name,
description,
avatar: undefined,
createdAt: new Date().toISOString(),
}
if (avatar) {
const blobRes = await apilib.uploadBlob(
rootStore,
avatar.path,
avatar.mime,
)
record.avatar = blobRes.data.blob
}
return await rootStore.agent.app.bsky.graph.list.create(
{
repo: rootStore.me.did,
},
record,
)
}
constructor(public rootStore: RootStoreModel, params: GetList.QueryParams) {
makeAutoObservable(
this,
{
rootStore: false,
params: false,
},
{autoBind: true},
)
this.params = params
}
get hasContent() {
return this.uri !== ''
}
get hasError() {
return this.error !== ''
}
get isEmpty() {
return this.hasLoaded && !this.hasContent
}
// public api
// =
async setup() {
await this._load()
}
async refresh() {
await this._load(true)
}
// state transitions
// =
_xLoading(isRefreshing = false) {
this.isLoading = true
this.isRefreshing = isRefreshing
this.error = ''
}
_xIdle(err?: any) {
this.isLoading = false
this.isRefreshing = false
this.hasLoaded = true
this.error = cleanError(err)
if (err) {
this.rootStore.log.error('Failed to fetch profile', err)
}
}
// loader functions
// =
async _load(isRefreshing = false) {
this._xLoading(isRefreshing)
try {
const res = await this.rootStore.agent.app.bsky.graph.getList(this.params)
this._replaceAll(res)
this._xIdle()
} catch (e: any) {
this._xIdle(e)
}
}
_replaceAll(res: GetList.Response) {
this.uri = res.data.list.uri
this.creator = res.data.list.creator
this.name = res.data.list.name
this.purpose = res.data.list.purpose
this.description = res.data.list.description
this.descriptionFacets = res.data.list.descriptionFacets
this.avatar = res.data.list.avatar
this.viewer = res.data.list.viewer
this.indexedAt = res.data.list.indexedAt
this.descriptionRichText = new RichText({
text: this.description || '',
facets: this.descriptionFacets,
})
}
}
-2
View File
@@ -18,8 +18,6 @@ import {
filterProfileLabels,
} from 'lib/labeling/helpers'
export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser'
export class ProfileViewerModel {
muted?: boolean
following?: string
+14 -10
View File
@@ -1,6 +1,6 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {AppBskyGraphDefs, RichText} from '@atproto/api'
import {AtUri, AppBskyGraphDefs, RichText} from '@atproto/api'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
import {RichText as RichTextCom} from '../util/text/RichText'
@@ -8,11 +8,6 @@ import {UserAvatar} from '../util/UserAvatar'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {
getProfileViewBasicLabelInfo,
getProfileModeration,
} from 'lib/labeling/helpers'
import {ModerationBehaviorCode} from 'lib/labeling/types'
export const ListCard = ({
testID,
@@ -29,6 +24,15 @@ export const ListCard = ({
}) => {
const pal = usePalette('default')
const rkey = React.useMemo(() => {
try {
const urip = new AtUri(list.uri)
return urip.rkey
} catch {
return ''
}
}, [list])
const descriptionRichText = React.useMemo(() => {
if (list.description) {
return new RichText({
@@ -48,7 +52,7 @@ export const ListCard = ({
noBorder && styles.outerNoBorder,
!noBg && pal.view,
]}
href={`/profile/${'list.author'}/lists/${'list.rkey'}`}
href={`/profile/${list.creator.did}/lists/${rkey}`}
title={list.name}
asAnchor
anchorNoUnderline>
@@ -65,10 +69,10 @@ export const ListCard = ({
{sanitizeDisplayName(list.name)}
</Text>
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
{list.purpose === 'app.bsky.graph.defs#blocklist' && 'Block list'}{' '}
by @TODO
{list.purpose === 'app.bsky.graph.defs#modlist' && 'Mute list'} by @
{list.creator.handle}
</Text>
{!!list.viewer?.blocked && (
{!!list.viewer?.muted && (
<View style={s.flexRow}>
<View style={[s.mt5, pal.btn, styles.pill]}>
<Text type="xs" style={pal.text}>
+7 -2
View File
@@ -14,6 +14,7 @@ import {Image as RNImage} from 'react-native-image-crop-picker'
import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {useStores} from 'state/index'
import {ListModel} from 'state/models/content/list'
import {s, colors, gradients} from 'lib/styles'
import {enforceLen} from 'lib/strings/helpers'
import {compressIfNeeded} from 'lib/media/manip'
@@ -71,9 +72,13 @@ export function Component({onCreate}: {onCreate?: (uri: string) => void}) {
setError('')
}
try {
// TODO
const res = await ListModel.createModList(store, {
name,
description,
avatar: newAvatar,
})
Toast.show('Mute-list created')
onCreate?.('todo')
onCreate?.(res.uri)
store.shell.closeModal()
} catch (e: any) {
if (isNetworkError(e)) {
+15 -3
View File
@@ -1,13 +1,18 @@
import React from 'react'
import {StyleSheet} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {
useFocusEffect,
useNavigation,
StackActions,
} from '@react-navigation/native'
import {AtUri} from '@atproto/api'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {EmptyStateWithButton} from 'view/com/util/EmptyStateWithButton'
import {MutelistsEmptyState} from 'view/com/lists/MutelistsEmptyState'
import {useStores} from 'state/index'
import {ListsListModel} from 'state/models/lists/lists-list'
import {ListsList} from 'view/com/lists/ListsList'
import {NavigationProp} from 'lib/routes/types'
import {usePalette} from 'lib/hooks/usePalette'
import {CenteredView} from 'view/com/util/Views'
import {ViewHeader} from 'view/com/util/ViewHeader'
@@ -20,6 +25,7 @@ type Props = NativeStackScreenProps<
export const ModerationMuteListsScreen = withAuthRequired(({route}: Props) => {
const pal = usePalette('default')
const store = useStores()
const navigation = useNavigation<NavigationProp>()
const mutelists: ListsListModel = React.useMemo(() => {
const list = new ListsListModel(store, 'mutelists')
@@ -37,7 +43,13 @@ export const ModerationMuteListsScreen = withAuthRequired(({route}: Props) => {
store.shell.openModal({
name: 'create-mute-list',
onCreate: (uri: string) => {
// TODO
try {
const urip = new AtUri(uri)
navigation.navigate('ProfileList', {
name: urip.hostname,
rkey: urip.rkey,
})
} catch {}
},
})
}, [store])