Implement mutelist creation
This commit is contained in:
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,8 +18,6 @@ import {
|
|||||||
filterProfileLabels,
|
filterProfileLabels,
|
||||||
} from 'lib/labeling/helpers'
|
} from 'lib/labeling/helpers'
|
||||||
|
|
||||||
export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser'
|
|
||||||
|
|
||||||
export class ProfileViewerModel {
|
export class ProfileViewerModel {
|
||||||
muted?: boolean
|
muted?: boolean
|
||||||
following?: string
|
following?: string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
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 {Link} from '../util/Link'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {RichText as RichTextCom} from '../util/text/RichText'
|
import {RichText as RichTextCom} from '../util/text/RichText'
|
||||||
@@ -8,11 +8,6 @@ import {UserAvatar} from '../util/UserAvatar'
|
|||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||||
import {
|
|
||||||
getProfileViewBasicLabelInfo,
|
|
||||||
getProfileModeration,
|
|
||||||
} from 'lib/labeling/helpers'
|
|
||||||
import {ModerationBehaviorCode} from 'lib/labeling/types'
|
|
||||||
|
|
||||||
export const ListCard = ({
|
export const ListCard = ({
|
||||||
testID,
|
testID,
|
||||||
@@ -29,6 +24,15 @@ export const ListCard = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const pal = usePalette('default')
|
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(() => {
|
const descriptionRichText = React.useMemo(() => {
|
||||||
if (list.description) {
|
if (list.description) {
|
||||||
return new RichText({
|
return new RichText({
|
||||||
@@ -48,7 +52,7 @@ export const ListCard = ({
|
|||||||
noBorder && styles.outerNoBorder,
|
noBorder && styles.outerNoBorder,
|
||||||
!noBg && pal.view,
|
!noBg && pal.view,
|
||||||
]}
|
]}
|
||||||
href={`/profile/${'list.author'}/lists/${'list.rkey'}`}
|
href={`/profile/${list.creator.did}/lists/${rkey}`}
|
||||||
title={list.name}
|
title={list.name}
|
||||||
asAnchor
|
asAnchor
|
||||||
anchorNoUnderline>
|
anchorNoUnderline>
|
||||||
@@ -65,10 +69,10 @@ export const ListCard = ({
|
|||||||
{sanitizeDisplayName(list.name)}
|
{sanitizeDisplayName(list.name)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
|
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
|
||||||
{list.purpose === 'app.bsky.graph.defs#blocklist' && 'Block list'}{' '}
|
{list.purpose === 'app.bsky.graph.defs#modlist' && 'Mute list'} by @
|
||||||
by @TODO
|
{list.creator.handle}
|
||||||
</Text>
|
</Text>
|
||||||
{!!list.viewer?.blocked && (
|
{!!list.viewer?.muted && (
|
||||||
<View style={s.flexRow}>
|
<View style={s.flexRow}>
|
||||||
<View style={[s.mt5, pal.btn, styles.pill]}>
|
<View style={[s.mt5, pal.btn, styles.pill]}>
|
||||||
<Text type="xs" style={pal.text}>
|
<Text type="xs" style={pal.text}>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {Image as RNImage} from 'react-native-image-crop-picker'
|
|||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
|
import {ListModel} from 'state/models/content/list'
|
||||||
import {s, colors, gradients} from 'lib/styles'
|
import {s, colors, gradients} from 'lib/styles'
|
||||||
import {enforceLen} from 'lib/strings/helpers'
|
import {enforceLen} from 'lib/strings/helpers'
|
||||||
import {compressIfNeeded} from 'lib/media/manip'
|
import {compressIfNeeded} from 'lib/media/manip'
|
||||||
@@ -71,9 +72,13 @@ export function Component({onCreate}: {onCreate?: (uri: string) => void}) {
|
|||||||
setError('')
|
setError('')
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// TODO
|
const res = await ListModel.createModList(store, {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
avatar: newAvatar,
|
||||||
|
})
|
||||||
Toast.show('Mute-list created')
|
Toast.show('Mute-list created')
|
||||||
onCreate?.('todo')
|
onCreate?.(res.uri)
|
||||||
store.shell.closeModal()
|
store.shell.closeModal()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (isNetworkError(e)) {
|
if (isNetworkError(e)) {
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet} from 'react-native'
|
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 {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||||
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
||||||
import {EmptyStateWithButton} from 'view/com/util/EmptyStateWithButton'
|
import {EmptyStateWithButton} from 'view/com/util/EmptyStateWithButton'
|
||||||
import {MutelistsEmptyState} from 'view/com/lists/MutelistsEmptyState'
|
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {ListsListModel} from 'state/models/lists/lists-list'
|
import {ListsListModel} from 'state/models/lists/lists-list'
|
||||||
import {ListsList} from 'view/com/lists/ListsList'
|
import {ListsList} from 'view/com/lists/ListsList'
|
||||||
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||||
@@ -20,6 +25,7 @@ type Props = NativeStackScreenProps<
|
|||||||
export const ModerationMuteListsScreen = withAuthRequired(({route}: Props) => {
|
export const ModerationMuteListsScreen = withAuthRequired(({route}: Props) => {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
|
const navigation = useNavigation<NavigationProp>()
|
||||||
|
|
||||||
const mutelists: ListsListModel = React.useMemo(() => {
|
const mutelists: ListsListModel = React.useMemo(() => {
|
||||||
const list = new ListsListModel(store, 'mutelists')
|
const list = new ListsListModel(store, 'mutelists')
|
||||||
@@ -37,7 +43,13 @@ export const ModerationMuteListsScreen = withAuthRequired(({route}: Props) => {
|
|||||||
store.shell.openModal({
|
store.shell.openModal({
|
||||||
name: 'create-mute-list',
|
name: 'create-mute-list',
|
||||||
onCreate: (uri: string) => {
|
onCreate: (uri: string) => {
|
||||||
// TODO
|
try {
|
||||||
|
const urip = new AtUri(uri)
|
||||||
|
navigation.navigate('ProfileList', {
|
||||||
|
name: urip.hostname,
|
||||||
|
rkey: urip.rkey,
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|||||||
Reference in New Issue
Block a user