mirror of https://github.com/tooot-app/app
Merge branch 'main' into candidate
This commit is contained in:
commit
171cfd0ead
|
@ -0,0 +1 @@
|
||||||
|
../../it/description.txt
|
|
@ -0,0 +1 @@
|
||||||
|
../../it/subtitle.txt
|
|
@ -1,5 +1,10 @@
|
||||||
tooot is an open source, simple yet elegant Mastodon mobile client.
|
tooot is an open source, simple yet elegant Mastodon mobile client. A Mastodon (https://joinmastodon.org/) account is required to use this app.
|
||||||
|
|
||||||
A Mastodon (https://joinmastodon.org/) account is required to use this app.
|
tooot supports:
|
||||||
|
- Cross platform, including iPadOS and MacOS
|
||||||
|
- Multiple accounts
|
||||||
|
- Dark mode or adapt to system
|
||||||
|
- Adjustable toot font size
|
||||||
|
- Push notification
|
||||||
|
|
||||||
If you have suggestions, please reach out to @tooot@xmflsct.com or support@tooot.ap.
|
If you have suggestions, please reach out to @tooot@xmflsct.com or support@tooot.app.
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
Enjoy toooting! This version includes following improvements and fixes:
|
Enjoy toooting! This version includes following improvements and fixes:
|
||||||
|
- Align filter experience with v4.0 and above
|
||||||
|
- Supports enlarging user's avatar and banner
|
||||||
|
- Fix iPad weird sizing (not optimisation)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
tooot è un client Mastodon semplice e open source. Per utilizzare questo client, devi disporre di un account Mastodon. (https://joinmastodon.org/).
|
||||||
|
|
||||||
|
Tooot supporta:
|
||||||
|
- Multipiattaforma, inclusi iPadOS e MacOS
|
||||||
|
- Accesso a più account
|
||||||
|
- Modalità scura o adattiva
|
||||||
|
- Dimensione del carattere del testo regolabile
|
||||||
|
- Notifiche push e altre funzioni
|
||||||
|
|
||||||
|
Per suggerimenti o commenti sull'utilizzo, contattare @tooot@xmflsct.com o support@tooot.app.
|
|
@ -0,0 +1 @@
|
||||||
|
Client open source per Mastodon
|
|
@ -1,11 +1,10 @@
|
||||||
tooot是一个专门为中文用户社区所打造的开源、简洁长毛象客户端。使用此客户端需要已经拥有一个长毛象(https://joinmastodon.org/)账号。
|
tooot起始于专注中文社区的简洁、开源长毛象手机客户端。使用此客户端需要已经拥有一个长毛象(https://joinmastodon.org/)账号。
|
||||||
|
|
||||||
tooot支持:
|
tooot支持:
|
||||||
- iPad
|
- 跨平台,及iPadOS、MacOS
|
||||||
- 多账号登录
|
- 多账号登录
|
||||||
- 黑暗或自适应模式
|
- 黑暗或自适应模式
|
||||||
- 可调整正文字体大小
|
- 可调正文字体尺寸
|
||||||
- 消息推送
|
- 消息推送
|
||||||
等功能。
|
|
||||||
|
|
||||||
如有使用建议或意见,请联系@tooot@xmflsct.com或者support@tooot.app。
|
如有使用建议或意见,请联系@tooot@xmflsct.com或者support@tooot.app。
|
|
@ -1 +1,4 @@
|
||||||
toooting愉快!此版本包括以下改进和修复:
|
toooting愉快!此版本包括以下改进和修复:
|
||||||
|
- 改进过滤体验,与v4.0以上版本一致
|
||||||
|
- 支持查看用户的头像和横幅图片
|
||||||
|
- 修复iPad部分尺寸问题(非优化)
|
||||||
|
|
|
@ -263,7 +263,8 @@ declare namespace Mastodon {
|
||||||
verified_at: string | null
|
verified_at: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filter = {
|
type Filter<T extends 'v1' | 'v2'> = T extends 'v2' ? Filter_V2 : Filter_V1
|
||||||
|
type Filter_V1 = {
|
||||||
id: string
|
id: string
|
||||||
phrase: string
|
phrase: string
|
||||||
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
|
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
|
||||||
|
@ -271,6 +272,25 @@ declare namespace Mastodon {
|
||||||
irreversible: boolean
|
irreversible: boolean
|
||||||
whole_word: boolean
|
whole_word: boolean
|
||||||
}
|
}
|
||||||
|
type Filter_V2 = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
|
||||||
|
expires_at?: string
|
||||||
|
filter_action: 'warn' | 'hide'
|
||||||
|
keywords: FilterKeyword[]
|
||||||
|
statuses: FilterStatus[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilterKeyword = { id: string; keyword: string; whole_word: boolean }
|
||||||
|
|
||||||
|
type FilterStatus = { id: string; status_id: string }
|
||||||
|
|
||||||
|
type FilterResult = {
|
||||||
|
filter: Filter<'v2'>
|
||||||
|
keyword_matches?: FilterKeyword['keyword'][]
|
||||||
|
status_matches?: FilterStatus['id'][]
|
||||||
|
}
|
||||||
|
|
||||||
type List = {
|
type List = {
|
||||||
id: string
|
id: string
|
||||||
|
@ -461,7 +481,7 @@ declare namespace Mastodon {
|
||||||
sensitive: boolean
|
sensitive: boolean
|
||||||
spoiler_text?: string
|
spoiler_text?: string
|
||||||
media_attachments: Attachment[]
|
media_attachments: Attachment[]
|
||||||
application: Application
|
application?: Application
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
mentions: Mention[]
|
mentions: Mention[]
|
||||||
|
@ -472,7 +492,7 @@ declare namespace Mastodon {
|
||||||
reblogs_count: number
|
reblogs_count: number
|
||||||
favourites_count: number
|
favourites_count: number
|
||||||
replies_count: number
|
replies_count: number
|
||||||
edited_at?: string // FEATURE edit_post
|
edited_at?: string
|
||||||
favourited: boolean
|
favourited: boolean
|
||||||
reblogged: boolean
|
reblogged: boolean
|
||||||
muted: boolean
|
muted: boolean
|
||||||
|
@ -488,6 +508,7 @@ declare namespace Mastodon {
|
||||||
card?: Card
|
card?: Card
|
||||||
language?: string
|
language?: string
|
||||||
text?: string
|
text?: string
|
||||||
|
filtered?: FilterResult[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusHistory = {
|
type StatusHistory = {
|
||||||
|
|
|
@ -98,7 +98,6 @@ const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlig
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{conversation.last_status ? (
|
{conversation.last_status ? (
|
||||||
<>
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
||||||
|
@ -107,10 +106,9 @@ const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlig
|
||||||
>
|
>
|
||||||
<TimelineContent />
|
<TimelineContent />
|
||||||
<TimelinePoll />
|
<TimelinePoll />
|
||||||
</View>
|
|
||||||
|
|
||||||
<TimelineActions />
|
<TimelineActions />
|
||||||
</>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</StatusContext.Provider>
|
</StatusContext.Provider>
|
||||||
|
|
|
@ -9,11 +9,12 @@ import TimelineCard from '@components/Timeline/Shared/Card'
|
||||||
import TimelineContent from '@components/Timeline/Shared/Content'
|
import TimelineContent from '@components/Timeline/Shared/Content'
|
||||||
import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault'
|
import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault'
|
||||||
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||||
|
import removeHTML from '@helpers/removeHTML'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useRef, useState } from 'react'
|
import React, { useRef, useState } from 'react'
|
||||||
|
@ -22,7 +23,7 @@ import { useSelector } from 'react-redux'
|
||||||
import * as ContextMenu from 'zeego/context-menu'
|
import * as ContextMenu from 'zeego/context-menu'
|
||||||
import StatusContext from './Shared/Context'
|
import StatusContext from './Shared/Context'
|
||||||
import TimelineFeedback from './Shared/Feedback'
|
import TimelineFeedback from './Shared/Feedback'
|
||||||
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
|
||||||
import TimelineFullConversation from './Shared/FullConversation'
|
import TimelineFullConversation from './Shared/FullConversation'
|
||||||
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
||||||
import TimelineTranslate from './Shared/Translate'
|
import TimelineTranslate from './Shared/Translate'
|
||||||
|
@ -47,12 +48,20 @@ const TimelineDefault: React.FC<Props> = ({
|
||||||
disableOnPress = false,
|
disableOnPress = false,
|
||||||
isConversation = false
|
isConversation = false
|
||||||
}) => {
|
}) => {
|
||||||
|
const status = item.reblog ? item.reblog : item
|
||||||
|
const rawContent = useRef<string[]>([])
|
||||||
|
if (highlighted) {
|
||||||
|
rawContent.current = [
|
||||||
|
removeHTML(status.content),
|
||||||
|
status.spoiler_text ? removeHTML(status.spoiler_text) : ''
|
||||||
|
].filter(c => c.length)
|
||||||
|
}
|
||||||
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
|
|
||||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||||
|
|
||||||
const status = item.reblog ? item.reblog : item
|
|
||||||
const ownAccount = status.account?.id === instanceAccount?.id
|
const ownAccount = status.account?.id === instanceAccount?.id
|
||||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||||
instanceAccount?.preferences?.['reading:expand:spoilers'] || false
|
instanceAccount?.preferences?.['reading:expand:spoilers'] || false
|
||||||
|
@ -60,17 +69,8 @@ const TimelineDefault: React.FC<Props> = ({
|
||||||
const spoilerHidden = status.spoiler_text?.length
|
const spoilerHidden = status.spoiler_text?.length
|
||||||
? !instanceAccount?.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
? !instanceAccount?.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||||
: false
|
: false
|
||||||
const copiableContent = useRef<{ content: string; complete: boolean }>({
|
|
||||||
content: '',
|
|
||||||
complete: false
|
|
||||||
})
|
|
||||||
const detectedLanguage = useRef<string>(status.language || '')
|
const detectedLanguage = useRef<string>(status.language || '')
|
||||||
|
|
||||||
const filtered = queryKey && shouldFilter({ copiableContent, status, queryKey })
|
|
||||||
if (queryKey && filtered && !highlighted) {
|
|
||||||
return <TimelineFiltered phrase={filtered} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainStyle: StyleProp<ViewStyle> = {
|
const mainStyle: StyleProp<ViewStyle> = {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: disableDetails
|
padding: disableDetails
|
||||||
|
@ -103,8 +103,9 @@ const TimelineDefault: React.FC<Props> = ({
|
||||||
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
||||||
paddingLeft: highlighted
|
paddingLeft: highlighted
|
||||||
? 0
|
? 0
|
||||||
: (disableDetails ? StyleConstants.Avatar.XS : StyleConstants.Avatar.M) +
|
: (disableDetails || isConversation
|
||||||
StyleConstants.Spacing.S,
|
? StyleConstants.Avatar.XS
|
||||||
|
: StyleConstants.Avatar.M) + StyleConstants.Spacing.S,
|
||||||
...(disableDetails && { marginTop: -StyleConstants.Spacing.S })
|
...(disableDetails && { marginTop: -StyleConstants.Spacing.S })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -115,9 +116,9 @@ const TimelineDefault: React.FC<Props> = ({
|
||||||
<TimelineFullConversation />
|
<TimelineFullConversation />
|
||||||
<TimelineTranslate />
|
<TimelineTranslate />
|
||||||
<TimelineFeedback />
|
<TimelineFeedback />
|
||||||
</View>
|
|
||||||
|
|
||||||
<TimelineActions />
|
<TimelineActions />
|
||||||
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -125,11 +126,36 @@ const TimelineDefault: React.FC<Props> = ({
|
||||||
visibility: status.visibility,
|
visibility: status.visibility,
|
||||||
type: 'status',
|
type: 'status',
|
||||||
url: status.url || status.uri,
|
url: status.url || status.uri,
|
||||||
copiableContent
|
rawContent
|
||||||
})
|
})
|
||||||
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||||
const mInstance = menuInstance({ status, queryKey, rootQueryKey })
|
const mInstance = menuInstance({ status, queryKey, rootQueryKey })
|
||||||
|
|
||||||
|
if (!ownAccount) {
|
||||||
|
let filterResults: FilteredProps['filterResults'] = []
|
||||||
|
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||||
|
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
|
||||||
|
if (hasFilterServerSide) {
|
||||||
|
if (status.filtered?.length) {
|
||||||
|
filterResults = status.filtered?.map(filter => filter.filter)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (queryKey) {
|
||||||
|
const checkFilter = shouldFilter({ queryKey, status })
|
||||||
|
if (checkFilter?.length) {
|
||||||
|
filterResults = checkFilter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queryKey && !highlighted && filterResults?.length && !filterRevealed) {
|
||||||
|
return !filterResults.filter(result => result.filter_action === 'hide').length ? (
|
||||||
|
<Pressable onPress={() => setFilterRevealed(!filterRevealed)}>
|
||||||
|
<TimelineFiltered filterResults={filterResults} />
|
||||||
|
</Pressable>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusContext.Provider
|
<StatusContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -139,7 +165,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||||
reblogStatus: item.reblog ? item : undefined,
|
reblogStatus: item.reblog ? item : undefined,
|
||||||
ownAccount,
|
ownAccount,
|
||||||
spoilerHidden,
|
spoilerHidden,
|
||||||
copiableContent,
|
rawContent,
|
||||||
detectedLanguage,
|
detectedLanguage,
|
||||||
highlighted,
|
highlighted,
|
||||||
inThread: queryKey?.[1].page === 'Toot',
|
inThread: queryKey?.[1].page === 'Toot',
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useRef, useState } from 'react'
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
|
@ -21,7 +21,7 @@ import { Pressable, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import * as ContextMenu from 'zeego/context-menu'
|
import * as ContextMenu from 'zeego/context-menu'
|
||||||
import StatusContext from './Shared/Context'
|
import StatusContext from './Shared/Context'
|
||||||
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
|
||||||
import TimelineFullConversation from './Shared/FullConversation'
|
import TimelineFullConversation from './Shared/FullConversation'
|
||||||
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
||||||
|
|
||||||
|
@ -47,21 +47,6 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||||
const spoilerHidden = notification.status?.spoiler_text?.length
|
const spoilerHidden = notification.status?.spoiler_text?.length
|
||||||
? !instanceAccount.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
? !instanceAccount.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||||
: false
|
: false
|
||||||
const copiableContent = useRef<{ content: string; complete: boolean }>({
|
|
||||||
content: '',
|
|
||||||
complete: false
|
|
||||||
})
|
|
||||||
|
|
||||||
const filtered =
|
|
||||||
notification.status &&
|
|
||||||
shouldFilter({
|
|
||||||
copiableContent,
|
|
||||||
status: notification.status,
|
|
||||||
queryKey
|
|
||||||
})
|
|
||||||
if (notification.status && filtered) {
|
|
||||||
return <TimelineFiltered phrase={filtered} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
|
@ -112,11 +97,11 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||||
<TimelineAttachment />
|
<TimelineAttachment />
|
||||||
<TimelineCard />
|
<TimelineCard />
|
||||||
<TimelineFullConversation />
|
<TimelineFullConversation />
|
||||||
|
|
||||||
|
<TimelineActions />
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<TimelineActions />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -124,20 +109,44 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||||
const mShare = menuShare({
|
const mShare = menuShare({
|
||||||
visibility: notification.status?.visibility,
|
visibility: notification.status?.visibility,
|
||||||
type: 'status',
|
type: 'status',
|
||||||
url: notification.status?.url || notification.status?.uri,
|
url: notification.status?.url || notification.status?.uri
|
||||||
copiableContent
|
|
||||||
})
|
})
|
||||||
const mStatus = menuStatus({ status: notification.status, queryKey })
|
const mStatus = menuStatus({ status: notification.status, queryKey })
|
||||||
const mInstance = menuInstance({ status: notification.status, queryKey })
|
const mInstance = menuInstance({ status: notification.status, queryKey })
|
||||||
|
|
||||||
|
if (!ownAccount) {
|
||||||
|
let filterResults: FilteredProps['filterResults'] = []
|
||||||
|
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||||
|
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
|
||||||
|
if (notification.status) {
|
||||||
|
if (hasFilterServerSide) {
|
||||||
|
if (notification.status.filtered?.length) {
|
||||||
|
filterResults = notification.status.filtered.map(filter => filter.filter)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const checkFilter = shouldFilter({ queryKey, status: notification.status })
|
||||||
|
if (checkFilter?.length) {
|
||||||
|
filterResults = checkFilter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterResults?.length && !filterRevealed) {
|
||||||
|
return !filterResults.filter(result => result.filter_action === 'hide').length ? (
|
||||||
|
<Pressable onPress={() => setFilterRevealed(!filterRevealed)}>
|
||||||
|
<TimelineFiltered filterResults={filterResults} />
|
||||||
|
</Pressable>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusContext.Provider
|
<StatusContext.Provider
|
||||||
value={{
|
value={{
|
||||||
queryKey,
|
queryKey,
|
||||||
status,
|
status,
|
||||||
ownAccount,
|
ownAccount,
|
||||||
spoilerHidden,
|
spoilerHidden
|
||||||
copiableContent
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContextMenu.Root>
|
<ContextMenu.Root>
|
||||||
|
|
|
@ -55,7 +55,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||||
firstPage?.links?.prev && {
|
firstPage?.links?.prev && {
|
||||||
...(firstPage.links.prev.isOffset
|
...(firstPage.links.prev.isOffset
|
||||||
? { offset: firstPage.links.prev.id }
|
? { offset: firstPage.links.prev.id }
|
||||||
: { max_id: firstPage.links.prev.id }),
|
: { min_id: firstPage.links.prev.id }),
|
||||||
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
||||||
limit: '3'
|
limit: '3'
|
||||||
},
|
},
|
||||||
|
|
|
@ -263,11 +263,6 @@ const TimelineActions: React.FC = () => {
|
||||||
}, [status.bookmarked])
|
}, [status.bookmarked])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
paddingLeft: highlighted ? 0 : StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View style={{ flexDirection: 'row' }}>
|
<View style={{ flexDirection: 'row' }}>
|
||||||
<Pressable
|
<Pressable
|
||||||
{...(highlighted
|
{...(highlighted
|
||||||
|
@ -320,7 +315,6 @@ const TimelineActions: React.FC = () => {
|
||||||
children={childrenBookmark}
|
children={childrenBookmark}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,6 @@ const TimelineAttachment = () => {
|
||||||
preview_url: attachment.preview_url,
|
preview_url: attachment.preview_url,
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
remote_url: attachment.remote_url,
|
remote_url: attachment.remote_url,
|
||||||
blurhash: attachment.blurhash,
|
|
||||||
width: attachment.meta?.original?.width,
|
width: attachment.meta?.original?.width,
|
||||||
height: attachment.meta?.original?.height
|
height: attachment.meta?.original?.height
|
||||||
}
|
}
|
||||||
|
@ -90,7 +89,6 @@ const TimelineAttachment = () => {
|
||||||
preview_url: attachment.preview_url,
|
preview_url: attachment.preview_url,
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
remote_url: attachment.remote_url,
|
remote_url: attachment.remote_url,
|
||||||
blurhash: attachment.blurhash,
|
|
||||||
width: attachment.meta?.original?.width,
|
width: attachment.meta?.original?.width,
|
||||||
height: attachment.meta?.original?.height
|
height: attachment.meta?.original?.height
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,7 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
|
||||||
style={{
|
style={{
|
||||||
borderRadius: StyleConstants.Avatar.M,
|
borderRadius: StyleConstants.Avatar.M,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginRight: StyleConstants.Spacing.S,
|
marginRight: StyleConstants.Spacing.S
|
||||||
marginLeft: isConversation ? StyleConstants.Avatar.M - StyleConstants.Avatar.XS : undefined
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
|
|
||||||
type ContextType = {
|
export type HighlightedStatusContextType = {}
|
||||||
|
|
||||||
|
type StatusContextType = {
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
rootQueryKey?: QueryKeyTimeline
|
rootQueryKey?: QueryKeyTimeline
|
||||||
|
|
||||||
|
@ -10,10 +12,7 @@ type ContextType = {
|
||||||
reblogStatus?: Mastodon.Status // When it is a reblog, pass the root status
|
reblogStatus?: Mastodon.Status // When it is a reblog, pass the root status
|
||||||
ownAccount?: boolean
|
ownAccount?: boolean
|
||||||
spoilerHidden?: boolean
|
spoilerHidden?: boolean
|
||||||
copiableContent?: React.MutableRefObject<{
|
rawContent?: React.MutableRefObject<string[]> // When highlighted, for translate, edit history
|
||||||
content: string
|
|
||||||
complete: boolean
|
|
||||||
}>
|
|
||||||
detectedLanguage?: React.MutableRefObject<string>
|
detectedLanguage?: React.MutableRefObject<string>
|
||||||
|
|
||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
|
@ -22,6 +21,6 @@ type ContextType = {
|
||||||
disableOnPress?: boolean
|
disableOnPress?: boolean
|
||||||
isConversation?: boolean
|
isConversation?: boolean
|
||||||
}
|
}
|
||||||
const StatusContext = createContext<ContextType>({} as ContextType)
|
const StatusContext = createContext<StatusContextType>({} as StatusContextType)
|
||||||
|
|
||||||
export default StatusContext
|
export default StatusContext
|
||||||
|
|
|
@ -1,19 +1,46 @@
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
|
import removeHTML from '@helpers/removeHTML'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getInstance, getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstance } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import htmlparser2 from 'htmlparser2-without-node-native'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
|
|
||||||
const TimelineFiltered = React.memo(
|
export interface FilteredProps {
|
||||||
({ phrase }: { phrase: string }) => {
|
filterResults: { title: string; filter_action: Mastodon.Filter<'v2'>['filter_action'] }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimelineFiltered: React.FC<FilteredProps> = ({ filterResults }) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
|
const main = () => {
|
||||||
|
if (!filterResults?.length) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
switch (typeof filterResults[0]) {
|
||||||
|
case 'string': // v1 filter
|
||||||
|
return <>{t('shared.filtered.match', { context: 'v1', phrase: filterResults[0] })}</>
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{t('shared.filtered.match', {
|
||||||
|
context: 'v2',
|
||||||
|
count: filterResults.length,
|
||||||
|
filters: filterResults.map(result => result.title).join(t('common:separator'))
|
||||||
|
})}
|
||||||
|
<CustomText
|
||||||
|
style={{ color: colors.blue }}
|
||||||
|
children={`\n${t('shared.filtered.reveal')}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ backgroundColor: colors.backgroundDefault }}>
|
<View style={{ backgroundColor: colors.backgroundDefault }}>
|
||||||
<CustomText
|
<CustomText
|
||||||
|
@ -25,67 +52,47 @@ const TimelineFiltered = React.memo(
|
||||||
paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('shared.filtered', { phrase })}
|
{main()}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
export const shouldFilter = ({
|
export const shouldFilter = ({
|
||||||
copiableContent,
|
queryKey,
|
||||||
status,
|
status
|
||||||
queryKey
|
|
||||||
}: {
|
}: {
|
||||||
copiableContent: React.MutableRefObject<{
|
|
||||||
content: string
|
|
||||||
complete: boolean
|
|
||||||
}>
|
|
||||||
status: Mastodon.Status
|
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
}): string | null => {
|
status: Pick<Mastodon.Status, 'content' | 'spoiler_text'>
|
||||||
|
}): FilteredProps['filterResults'] | undefined => {
|
||||||
const page = queryKey[1]
|
const page = queryKey[1]
|
||||||
const instance = getInstance(store.getState())
|
const instance = getInstance(store.getState())
|
||||||
const ownAccount = getInstanceAccount(store.getState())?.id === status.account?.id
|
|
||||||
|
|
||||||
let shouldFilter: string | null = null
|
let returnFilter: FilteredProps['filterResults'] | undefined
|
||||||
|
|
||||||
if (!ownAccount) {
|
const rawContentCombined = [
|
||||||
let rawContent = ''
|
removeHTML(status.content),
|
||||||
const parser = new htmlparser2.Parser({
|
status.spoiler_text ? removeHTML(status.spoiler_text) : ''
|
||||||
ontext: (text: string) => {
|
]
|
||||||
if (!copiableContent.current.complete) {
|
.filter(c => c.length)
|
||||||
copiableContent.current.content = copiableContent.current.content + text
|
.join(`\n`)
|
||||||
}
|
const checkFilter = (filter: Mastodon.Filter<'v1'>) => {
|
||||||
|
|
||||||
rawContent = rawContent + text
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (status.spoiler_text) {
|
|
||||||
parser.write(status.spoiler_text)
|
|
||||||
rawContent = rawContent + `\n\n`
|
|
||||||
}
|
|
||||||
parser.write(status.content)
|
|
||||||
parser.end()
|
|
||||||
|
|
||||||
const checkFilter = (filter: Mastodon.Filter) => {
|
|
||||||
const escapedPhrase = filter.phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
|
const escapedPhrase = filter.phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
|
||||||
switch (filter.whole_word) {
|
switch (filter.whole_word) {
|
||||||
case true:
|
case true:
|
||||||
if (new RegExp(`\\b${escapedPhrase}\\b`, 'i').test(rawContent)) {
|
if (new RegExp(`\\b${escapedPhrase}\\b`, 'i').test(rawContentCombined)) {
|
||||||
shouldFilter = filter.phrase
|
returnFilter = [{ title: filter.phrase, filter_action: 'warn' }]
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case false:
|
case false:
|
||||||
if (new RegExp(escapedPhrase, 'i').test(rawContent)) {
|
if (new RegExp(escapedPhrase, 'i').test(rawContentCombined)) {
|
||||||
shouldFilter = filter.phrase
|
returnFilter = [{ title: filter.phrase, filter_action: 'warn' }]
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
instance?.filters?.forEach(filter => {
|
instance?.filters?.forEach(filter => {
|
||||||
if (shouldFilter) {
|
if (returnFilter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (filter.expires_at) {
|
if (filter.expires_at) {
|
||||||
|
@ -100,30 +107,27 @@ export const shouldFilter = ({
|
||||||
case 'List':
|
case 'List':
|
||||||
case 'Account':
|
case 'Account':
|
||||||
if (filter.context.includes('home')) {
|
if (filter.context.includes('home')) {
|
||||||
checkFilter(filter)
|
checkFilter(filter as Mastodon.Filter<'v1'>)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Notifications':
|
case 'Notifications':
|
||||||
if (filter.context.includes('notifications')) {
|
if (filter.context.includes('notifications')) {
|
||||||
checkFilter(filter)
|
checkFilter(filter as Mastodon.Filter<'v1'>)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'LocalPublic':
|
case 'LocalPublic':
|
||||||
if (filter.context.includes('public')) {
|
if (filter.context.includes('public')) {
|
||||||
checkFilter(filter)
|
checkFilter(filter as Mastodon.Filter<'v1'>)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Toot':
|
case 'Toot':
|
||||||
if (filter.context.includes('thread')) {
|
if (filter.context.includes('thread')) {
|
||||||
checkFilter(filter)
|
checkFilter(filter as Mastodon.Filter<'v1'>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
copiableContent.current.complete = true
|
return returnFilter
|
||||||
}
|
|
||||||
|
|
||||||
return shouldFilter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimelineFiltered
|
export default TimelineFiltered
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||||
import StatusContext from './Context'
|
import StatusContext from './Context'
|
||||||
|
|
||||||
const TimelineHeaderAndroid: React.FC = () => {
|
const TimelineHeaderAndroid: React.FC = () => {
|
||||||
const { queryKey, rootQueryKey, status, disableDetails, disableOnPress } =
|
const { queryKey, rootQueryKey, status, disableDetails, disableOnPress, rawContent } =
|
||||||
useContext(StatusContext)
|
useContext(StatusContext)
|
||||||
|
|
||||||
if (Platform.OS !== 'android' || !status || disableDetails || disableOnPress) return null
|
if (Platform.OS !== 'android' || !status || disableDetails || disableOnPress) return null
|
||||||
|
@ -21,7 +21,8 @@ const TimelineHeaderAndroid: React.FC = () => {
|
||||||
const mShare = menuShare({
|
const mShare = menuShare({
|
||||||
visibility: status.visibility,
|
visibility: status.visibility,
|
||||||
type: 'status',
|
type: 'status',
|
||||||
url: status.url || status.uri
|
url: status.url || status.uri,
|
||||||
|
rawContent
|
||||||
})
|
})
|
||||||
const mAccount = menuAccount({
|
const mAccount = menuAccount({
|
||||||
type: 'status',
|
type: 'status',
|
||||||
|
|
|
@ -16,7 +16,7 @@ import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||||
|
|
||||||
const TimelineHeaderDefault: React.FC = () => {
|
const TimelineHeaderDefault: React.FC = () => {
|
||||||
const { queryKey, rootQueryKey, status, copiableContent, highlighted, disableDetails } =
|
const { queryKey, rootQueryKey, status, highlighted, disableDetails, rawContent } =
|
||||||
useContext(StatusContext)
|
useContext(StatusContext)
|
||||||
if (!status) return null
|
if (!status) return null
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ const TimelineHeaderDefault: React.FC = () => {
|
||||||
visibility: status.visibility,
|
visibility: status.visibility,
|
||||||
type: 'status',
|
type: 'status',
|
||||||
url: status.url || status.uri,
|
url: status.url || status.uri,
|
||||||
copiableContent
|
rawContent
|
||||||
})
|
})
|
||||||
const mAccount = menuAccount({
|
const mAccount = menuAccount({
|
||||||
type: 'status',
|
type: 'status',
|
||||||
|
|
|
@ -13,38 +13,19 @@ import { Circle } from 'react-native-animated-spinkit'
|
||||||
import StatusContext from './Context'
|
import StatusContext from './Context'
|
||||||
|
|
||||||
const TimelineTranslate = () => {
|
const TimelineTranslate = () => {
|
||||||
const { status, highlighted, copiableContent, detectedLanguage } = useContext(StatusContext)
|
const { status, highlighted, rawContent, detectedLanguage } = useContext(StatusContext)
|
||||||
if (!status || !highlighted) return null
|
if (!status || !highlighted || !rawContent?.current.length) return null
|
||||||
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const backupTextProcessing = (): string[] => {
|
|
||||||
const text = status.spoiler_text ? [status.spoiler_text, status.content] : [status.content]
|
|
||||||
|
|
||||||
for (const i in text) {
|
|
||||||
for (const emoji of status.emojis) {
|
|
||||||
text[i] = text[i].replaceAll(`:${emoji.shortcode}:`, ' ')
|
|
||||||
}
|
|
||||||
text[i] = text[i]
|
|
||||||
.replace(/(<([^>]+)>)/gi, ' ')
|
|
||||||
.replace(/@.*? /gi, ' ')
|
|
||||||
.replace(/#.*? /gi, ' ')
|
|
||||||
.replace(/http(s):\/\/.*? /gi, ' ')
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
const text = copiableContent?.current.content
|
|
||||||
? [copiableContent?.current.content]
|
|
||||||
: backupTextProcessing()
|
|
||||||
|
|
||||||
const [detected, setDetected] = useState<{
|
const [detected, setDetected] = useState<{
|
||||||
language: string
|
language: string
|
||||||
confidence: number
|
confidence: number
|
||||||
}>({ language: status.language || '', confidence: 0 })
|
}>({ language: status.language || '', confidence: 0 })
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const detect = async () => {
|
const detect = async () => {
|
||||||
const result = await detectLanguage(text.join('\n\n'))
|
const result = await detectLanguage(rawContent.current.join('\n\n'))
|
||||||
if (result) {
|
if (result) {
|
||||||
setDetected(result)
|
setDetected(result)
|
||||||
if (detectedLanguage) {
|
if (detectedLanguage) {
|
||||||
|
@ -64,7 +45,7 @@ const TimelineTranslate = () => {
|
||||||
const { refetch, data, isFetching, isSuccess, isError } = useTranslateQuery({
|
const { refetch, data, isFetching, isSuccess, isError } = useTranslateQuery({
|
||||||
source: detected.language,
|
source: detected.language,
|
||||||
target: targetLanguage,
|
target: targetLanguage,
|
||||||
text,
|
text: rawContent.current,
|
||||||
options: { enabled }
|
options: { enabled }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,7 @@ const menuShare = (
|
||||||
params:
|
params:
|
||||||
| {
|
| {
|
||||||
visibility?: Mastodon.Status['visibility']
|
visibility?: Mastodon.Status['visibility']
|
||||||
copiableContent?: React.MutableRefObject<{
|
rawContent?: React.MutableRefObject<string[]>
|
||||||
content?: string | undefined
|
|
||||||
complete: boolean
|
|
||||||
}>
|
|
||||||
type: 'status'
|
type: 'status'
|
||||||
url?: string
|
url?: string
|
||||||
}
|
}
|
||||||
|
@ -48,17 +45,17 @@ const menuShare = (
|
||||||
icon: 'square.and.arrow.up'
|
icon: 'square.and.arrow.up'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (params.type === 'status' && Platform.OS === 'ios')
|
if (params.type === 'status')
|
||||||
menus[0].push({
|
menus[0].push({
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
item: {
|
item: {
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
Clipboard.setString(params.copiableContent?.current.content || '')
|
Clipboard.setString(params.rawContent?.current.join(`\n\n`) || '')
|
||||||
displayMessage({ type: 'success', message: t(`copy.succeed`) })
|
displayMessage({ type: 'success', message: t(`copy.succeed`) })
|
||||||
},
|
},
|
||||||
disabled: false,
|
disabled: false,
|
||||||
destructive: false,
|
destructive: false,
|
||||||
hidden: !params.copiableContent?.current.content?.length
|
hidden: !params.rawContent?.current.length
|
||||||
},
|
},
|
||||||
title: t('copy.action'),
|
title: t('copy.action'),
|
||||||
icon: 'doc.on.doc'
|
icon: 'doc.on.doc'
|
||||||
|
|
|
@ -42,5 +42,9 @@
|
||||||
{
|
{
|
||||||
"feature": "notification_type_admin_report",
|
"feature": "notification_type_admin_report",
|
||||||
"version": 4.0
|
"version": 4.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feature": "filter_server_side",
|
||||||
|
"version": 4.0
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -6,6 +6,9 @@ const removeHTML = (text: string): string => {
|
||||||
const parser = new htmlparser2.Parser({
|
const parser = new htmlparser2.Parser({
|
||||||
ontext: (text: string) => {
|
ontext: (text: string) => {
|
||||||
raw = raw + text
|
raw = raw + text
|
||||||
|
},
|
||||||
|
onclosetag: (tag: string) => {
|
||||||
|
if (['p', 'br'].includes(tag)) raw = raw + `\n`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,12 @@
|
||||||
"content": {
|
"content": {
|
||||||
"expandHint": "Hidden content"
|
"expandHint": "Hidden content"
|
||||||
},
|
},
|
||||||
"filtered": "Filtered: {{phrase}}.",
|
"filtered": {
|
||||||
|
"reveal": "Show anyway",
|
||||||
|
"match_v1": "Filtered: {{phrase}}.",
|
||||||
|
"match_v2_one": "Filtered by {{filters}}.",
|
||||||
|
"match_v2_other": "Filtered by {{count}} filters, {{filters}}."
|
||||||
|
},
|
||||||
"fullConversation": "Read conversations",
|
"fullConversation": "Read conversations",
|
||||||
"translate": {
|
"translate": {
|
||||||
"default": "Translate",
|
"default": "Translate",
|
||||||
|
|
|
@ -24,7 +24,7 @@ const ScreenActions = ({
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
const DEFAULT_VALUE = 350
|
const DEFAULT_VALUE = 350
|
||||||
const screenHeight = Dimensions.get('screen').height
|
const screenHeight = Dimensions.get('window').height
|
||||||
const panY = useSharedValue(DEFAULT_VALUE)
|
const panY = useSharedValue(DEFAULT_VALUE)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
panY.value = withTiming(0)
|
panY.value = withTiming(0)
|
||||||
|
|
|
@ -61,7 +61,7 @@ const ScreenAnnouncements: React.FC<RootStackScreenProps<'Screen-Announcements'>
|
||||||
<View
|
<View
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
width: Dimensions.get('screen').width,
|
width: Dimensions.get('window').width,
|
||||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||||
marginVertical: StyleConstants.Spacing.Global.PagePadding,
|
marginVertical: StyleConstants.Spacing.Global.PagePadding,
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
@ -200,7 +200,7 @@ const ScreenAnnouncements: React.FC<RootStackScreenProps<'Screen-Announcements'>
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: Dimensions.get('screen').width,
|
width: Dimensions.get('window').width,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -142,12 +142,12 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
width:
|
width:
|
||||||
(Dimensions.get('screen').width -
|
(Dimensions.get('window').width -
|
||||||
StyleConstants.Spacing.Global.PagePadding * 2 -
|
StyleConstants.Spacing.Global.PagePadding * 2 -
|
||||||
StyleConstants.Spacing.S * 3) /
|
StyleConstants.Spacing.S * 3) /
|
||||||
4,
|
4,
|
||||||
height:
|
height:
|
||||||
(Dimensions.get('screen').width -
|
(Dimensions.get('window').width -
|
||||||
StyleConstants.Spacing.Global.PagePadding * 2 -
|
StyleConstants.Spacing.Global.PagePadding * 2 -
|
||||||
StyleConstants.Spacing.S * 3) /
|
StyleConstants.Spacing.S * 3) /
|
||||||
4,
|
4,
|
||||||
|
|
|
@ -33,7 +33,6 @@ const ComposeTextInput: React.FC = () => {
|
||||||
paddingBottom: StyleConstants.Spacing.M,
|
paddingBottom: StyleConstants.Spacing.M,
|
||||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
color: colors.primaryDefault,
|
color: colors.primaryDefault,
|
||||||
borderBottomColor: colors.border,
|
|
||||||
fontSize: adaptedFontsize,
|
fontSize: adaptedFontsize,
|
||||||
lineHeight: adaptedLineheight
|
lineHeight: adaptedLineheight
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -25,7 +25,7 @@ const ZoomFlatList = createZoomListComponent(FlatList)
|
||||||
|
|
||||||
const ScreenImagesViewer = ({
|
const ScreenImagesViewer = ({
|
||||||
route: {
|
route: {
|
||||||
params: { imageUrls, id }
|
params: { imageUrls, id, hideCounter }
|
||||||
},
|
},
|
||||||
navigation
|
navigation
|
||||||
}: RootStackScreenProps<'Screen-ImagesViewer'>) => {
|
}: RootStackScreenProps<'Screen-ImagesViewer'>) => {
|
||||||
|
@ -34,8 +34,8 @@ const ScreenImagesViewer = ({
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCREEN_WIDTH = Dimensions.get('screen').width
|
const WINDOW_WIDTH = Dimensions.get('window').width
|
||||||
const SCREEN_HEIGHT = Dimensions.get('screen').height
|
const WINDOW_HEIGHT = Dimensions.get('window').height
|
||||||
|
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
|
@ -85,13 +85,13 @@ const ScreenImagesViewer = ({
|
||||||
}: {
|
}: {
|
||||||
item: RootStackScreenProps<'Screen-ImagesViewer'>['route']['params']['imageUrls'][0]
|
item: RootStackScreenProps<'Screen-ImagesViewer'>['route']['params']['imageUrls'][0]
|
||||||
}) => {
|
}) => {
|
||||||
const screenRatio = SCREEN_WIDTH / SCREEN_HEIGHT
|
const screenRatio = WINDOW_WIDTH / WINDOW_HEIGHT
|
||||||
const imageRatio = item.width && item.height ? item.width / item.height : 1
|
const imageRatio = item.width && item.height ? item.width / item.height : 1
|
||||||
const imageWidth = item.width || 100
|
const imageWidth = item.width || 100
|
||||||
const imageHeight = item.height || 100
|
const imageHeight = item.height || 100
|
||||||
|
|
||||||
const maxWidthScale = item.width ? (item.width / SCREEN_WIDTH / PixelRatio.get()) * 4 : 0
|
const maxWidthScale = item.width ? (item.width / WINDOW_WIDTH / PixelRatio.get()) * 4 : 0
|
||||||
const maxHeightScale = item.height ? (item.height / SCREEN_WIDTH / PixelRatio.get()) * 4 : 0
|
const maxHeightScale = item.height ? (item.height / WINDOW_WIDTH / PixelRatio.get()) * 4 : 0
|
||||||
const max = Math.max.apply(Math, [maxWidthScale, maxHeightScale, 4])
|
const max = Math.max.apply(Math, [maxWidthScale, maxHeightScale, 4])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -109,8 +109,8 @@ const ScreenImagesViewer = ({
|
||||||
children={
|
children={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: SCREEN_WIDTH,
|
width: WINDOW_WIDTH,
|
||||||
height: SCREEN_HEIGHT,
|
height: WINDOW_HEIGHT,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
@ -121,12 +121,12 @@ const ScreenImagesViewer = ({
|
||||||
dimension={{
|
dimension={{
|
||||||
width:
|
width:
|
||||||
screenRatio > imageRatio
|
screenRatio > imageRatio
|
||||||
? (SCREEN_HEIGHT / imageHeight) * imageWidth
|
? (WINDOW_HEIGHT / imageHeight) * imageWidth
|
||||||
: SCREEN_WIDTH,
|
: WINDOW_WIDTH,
|
||||||
height:
|
height:
|
||||||
screenRatio > imageRatio
|
screenRatio > imageRatio
|
||||||
? SCREEN_HEIGHT
|
? WINDOW_HEIGHT
|
||||||
: (SCREEN_WIDTH / imageWidth) * imageHeight
|
: (WINDOW_WIDTH / imageWidth) * imageHeight
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -159,7 +159,9 @@ const ScreenImagesViewer = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HeaderLeft content='X' native={false} background onPress={() => navigation.goBack()} />
|
<HeaderLeft content='X' native={false} background onPress={() => navigation.goBack()} />
|
||||||
|
{!hideCounter ? (
|
||||||
<HeaderCenter inverted content={`${currentIndex + 1} / ${imageUrls.length}`} />
|
<HeaderCenter inverted content={`${currentIndex + 1} / ${imageUrls.length}`} />
|
||||||
|
) : null}
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
accessibilityLabel={t('content.actions.accessibilityLabel')}
|
accessibilityLabel={t('content.actions.accessibilityLabel')}
|
||||||
accessibilityHint={t('content.actions.accessibilityHint')}
|
accessibilityHint={t('content.actions.accessibilityHint')}
|
||||||
|
@ -215,8 +217,8 @@ const ScreenImagesViewer = ({
|
||||||
}}
|
}}
|
||||||
initialScrollIndex={initialIndex}
|
initialScrollIndex={initialIndex}
|
||||||
getItemLayout={(_, index) => ({
|
getItemLayout={(_, index) => ({
|
||||||
length: SCREEN_WIDTH,
|
length: WINDOW_WIDTH,
|
||||||
offset: SCREEN_WIDTH * index,
|
offset: WINDOW_WIDTH * index,
|
||||||
index
|
index
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -46,18 +46,14 @@ const TabMePush: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkPush = async () => {
|
const checkPush = async () => {
|
||||||
switch (Platform.OS) {
|
const permissions = await Notifications.getPermissionsAsync()
|
||||||
case 'ios':
|
setPushEnabled(permissions.granted)
|
||||||
const settings = await Notifications.getPermissionsAsync()
|
setPushCanAskAgain(permissions.canAskAgain)
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
setPushEnabled(settings.granted)
|
|
||||||
setPushCanAskAgain(settings.canAskAgain)
|
if (Platform.OS === 'android') {
|
||||||
break
|
|
||||||
case 'android':
|
|
||||||
await setChannels(instance)
|
await setChannels(instance)
|
||||||
layoutAnimation()
|
|
||||||
dispatch(retrieveExpoToken())
|
dispatch(retrieveExpoToken())
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,7 @@ import CustomText from '@components/Text'
|
||||||
import TimelineDefault from '@components/Timeline/Default'
|
import TimelineDefault from '@components/Timeline/Default'
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import {
|
import { changeFontsize, getSettingsFontsize, SettingsState } from '@utils/slices/settingsSlice'
|
||||||
changeFontsize,
|
|
||||||
getSettingsFontsize,
|
|
||||||
SettingsState
|
|
||||||
} from '@utils/slices/settingsSlice'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { adaptiveScale } from '@utils/styles/scaling'
|
import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
@ -34,9 +30,7 @@ export const mapFontsizeToName = (size: SettingsState['fontsize']) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TabMeSettingsFontsize: React.FC<
|
const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fontsize'>> = () => {
|
||||||
TabMeStackScreenProps<'Tab-Me-Settings-Fontsize'>
|
|
||||||
> = () => {
|
|
||||||
const { colors, theme } = useTheme()
|
const { colors, theme } = useTheme()
|
||||||
const { t } = useTranslation('screenTabs')
|
const { t } = useTranslation('screenTabs')
|
||||||
const initialSize = useSelector(getSettingsFontsize)
|
const initialSize = useSelector(getSettingsFontsize)
|
||||||
|
@ -86,8 +80,7 @@ const TabMeSettingsFontsize: React.FC<
|
||||||
marginBottom: StyleConstants.Spacing.M,
|
marginBottom: StyleConstants.Spacing.M,
|
||||||
fontSize: adaptiveScale(StyleConstants.Font.Size.M, size),
|
fontSize: adaptiveScale(StyleConstants.Font.Size.M, size),
|
||||||
lineHeight: adaptiveScale(StyleConstants.Font.LineHeight.M, size),
|
lineHeight: adaptiveScale(StyleConstants.Font.LineHeight.M, size),
|
||||||
color:
|
color: initialSize === size ? colors.primaryDefault : colors.secondary,
|
||||||
initialSize === size ? colors.primaryDefault : colors.secondary,
|
|
||||||
borderWidth: StyleSheet.hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: colors.border
|
borderColor: colors.border
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -88,7 +88,7 @@ const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public
|
||||||
renderTabBar={() => null}
|
renderTabBar={() => null}
|
||||||
onIndexChange={index => setSegment(index)}
|
onIndexChange={index => setSegment(index)}
|
||||||
navigationState={{ index: segment, routes }}
|
navigationState={{ index: segment, routes }}
|
||||||
initialLayout={{ width: Dimensions.get('screen').width }}
|
initialLayout={{ width: Dimensions.get('window').width }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,16 @@ import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import Timeline from '@components/Timeline'
|
import Timeline from '@components/Timeline'
|
||||||
import TimelineDefault from '@components/Timeline/Default'
|
import TimelineDefault from '@components/Timeline/Default'
|
||||||
import SegmentedControl from '@react-native-community/segmented-control'
|
import SegmentedControl from '@react-native-community/segmented-control'
|
||||||
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Text, View } from 'react-native'
|
import { Text, View } from 'react-native'
|
||||||
import { useSharedValue } from 'react-native-reanimated'
|
import { useSharedValue } from 'react-native-reanimated'
|
||||||
import { useIsFetching } from '@tanstack/react-query'
|
|
||||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||||
import AccountAttachments from './Account/Attachments'
|
import AccountAttachments from './Account/Attachments'
|
||||||
import AccountHeader from './Account/Header'
|
import AccountHeader from './Account/Header'
|
||||||
|
@ -87,35 +87,29 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||||
|
|
||||||
const scrollY = useSharedValue(0)
|
const scrollY = useSharedValue(0)
|
||||||
|
|
||||||
|
const queryClient = useQueryClient()
|
||||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>([
|
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>([
|
||||||
'Timeline',
|
'Timeline',
|
||||||
{ page: 'Account', account: account.id, exclude_reblogs: true, only_media: false }
|
{ page: 'Account', account: account.id, exclude_reblogs: true, only_media: false }
|
||||||
])
|
])
|
||||||
const page = queryKey[1]
|
const page = queryKey[1]
|
||||||
const isFetchingTimeline = useIsFetching(queryKey)
|
|
||||||
const fetchedTimeline = useRef(false)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isFetchingTimeline && !fetchedTimeline.current) {
|
|
||||||
fetchedTimeline.current = true
|
|
||||||
}
|
|
||||||
}, [isFetchingTimeline, fetchedTimeline.current])
|
|
||||||
|
|
||||||
|
const [segment, setSegment] = useState<number>(0)
|
||||||
const ListHeaderComponent = useMemo(() => {
|
const ListHeaderComponent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={{ borderBottomWidth: 1, borderBottomColor: colors.border }}>
|
<View style={{ borderBottomWidth: 1, borderBottomColor: colors.border }}>
|
||||||
<AccountHeader account={data} />
|
<AccountHeader account={data} />
|
||||||
<AccountInformation account={data} />
|
<AccountInformation account={data} />
|
||||||
{!data?.suspended && fetchedTimeline.current ? (
|
{!data?.suspended ? <AccountAttachments account={data} /> : null}
|
||||||
<AccountAttachments account={data} />
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
{!data?.suspended ? (
|
{!data?.suspended ? (
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
appearance={mode}
|
appearance={mode}
|
||||||
values={[t('shared.account.toots.default'), t('shared.account.toots.all')]}
|
values={[t('shared.account.toots.default'), t('shared.account.toots.all')]}
|
||||||
selectedIndex={page.page === 'Account' ? 0 : 1}
|
selectedIndex={segment}
|
||||||
onChange={({ nativeEvent }) => {
|
onChange={({ nativeEvent }) => {
|
||||||
|
setSegment(nativeEvent.selectedSegmentIndex)
|
||||||
switch (nativeEvent.selectedSegmentIndex) {
|
switch (nativeEvent.selectedSegmentIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
setQueryKey([
|
setQueryKey([
|
||||||
|
@ -171,7 +165,7 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}, [data, fetchedTimeline.current, queryKey[1].page, mode])
|
}, [segment, data, queryKey[1].page, mode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -187,7 +181,9 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||||
renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
renderItem: ({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||||
onScroll: ({ nativeEvent }) => (scrollY.value = nativeEvent.contentOffset.y),
|
onScroll: ({ nativeEvent }) => (scrollY.value = nativeEvent.contentOffset.y),
|
||||||
ListHeaderComponent,
|
ListHeaderComponent,
|
||||||
maintainVisibleContentPosition: undefined
|
maintainVisibleContentPosition: undefined,
|
||||||
|
onRefresh: () => queryClient.refetchQueries(queryKey),
|
||||||
|
refreshing: queryClient.getQueryState(queryKey)?.fetchStatus === 'fetching'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,10 +3,10 @@ import Icon from '@components/Icon'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Dimensions, ListRenderItem, Pressable, View } from 'react-native'
|
import { Dimensions, ListRenderItem, Pressable, View } from 'react-native'
|
||||||
import { FlatList } from 'react-native-gesture-handler'
|
import { FlatList } from 'react-native-gesture-handler'
|
||||||
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||||
|
@ -23,23 +23,14 @@ const AccountAttachments: React.FC<Props> = ({ account }) => {
|
||||||
|
|
||||||
const DISPLAY_AMOUNT = 6
|
const DISPLAY_AMOUNT = 6
|
||||||
|
|
||||||
const width = (Dimensions.get('screen').width - StyleConstants.Spacing.Global.PagePadding * 2) / 4
|
const width = (Dimensions.get('window').width - StyleConstants.Spacing.Global.PagePadding * 2) / 4
|
||||||
|
|
||||||
const queryKeyParams: QueryKeyTimeline[1] = {
|
const { data } = useTimelineQuery({
|
||||||
page: 'Account',
|
page: 'Account',
|
||||||
account: account.id,
|
account: account.id,
|
||||||
exclude_reblogs: false,
|
exclude_reblogs: false,
|
||||||
only_media: true
|
only_media: true
|
||||||
}
|
|
||||||
const { data, refetch } = useTimelineQuery({
|
|
||||||
...queryKeyParams,
|
|
||||||
options: { enabled: false }
|
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
|
||||||
if (account?.id) {
|
|
||||||
refetch()
|
|
||||||
}
|
|
||||||
}, [account])
|
|
||||||
|
|
||||||
const flattenData = data?.pages
|
const flattenData = data?.pages
|
||||||
? data.pages
|
? data.pages
|
||||||
|
|
|
@ -1,47 +1,45 @@
|
||||||
import Button from '@components/Button'
|
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
|
import navigationRef from '@helpers/navigationRef'
|
||||||
|
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dimensions, View } from 'react-native'
|
import { Dimensions, Image, Pressable } from 'react-native'
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account?: Mastodon.Account
|
account?: Mastodon.Account
|
||||||
edit?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountHeader = React.memo(
|
const AccountHeader: React.FC<Props> = ({ account }) => {
|
||||||
({ account, edit }: Props) => {
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const topInset = useSafeAreaInsets().top
|
const topInset = useSafeAreaInsets().top
|
||||||
|
|
||||||
|
useSelector(getInstanceActive)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<Pressable
|
||||||
|
onPress={() => {
|
||||||
|
if (account) {
|
||||||
|
Image.getSize(account.header, (width, height) =>
|
||||||
|
navigationRef.navigate('Screen-ImagesViewer', {
|
||||||
|
imageUrls: [{ id: 'avatar', url: account.header, width, height }],
|
||||||
|
id: 'avatar',
|
||||||
|
hideCounter: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
uri={{ original: account?.header, static: account?.header_static }}
|
uri={{ original: account?.header, static: account?.header_static }}
|
||||||
style={{
|
style={{
|
||||||
height: Dimensions.get('screen').width / 3 + topInset,
|
height: Dimensions.get('window').width / 3 + topInset,
|
||||||
backgroundColor: colors.disabled
|
backgroundColor: colors.disabled
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{edit ? (
|
</Pressable>
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
alignContent: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type='icon' content='Edit' round onPress={() => {}} />
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(_, next) => next.account === undefined
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default AccountHeader
|
export default AccountHeader
|
||||||
|
|
|
@ -1,28 +1,37 @@
|
||||||
import Button from '@components/Button'
|
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
|
import navigationRef from '@helpers/navigationRef'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
myInfo: boolean
|
myInfo: boolean
|
||||||
edit?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo, edit }) => {
|
const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
useSelector(getInstanceActive)
|
useSelector(getInstanceActive)
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
disabled={!myInfo}
|
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
myInfo && account && navigation.push('Tab-Shared-Account', { account })
|
if (account) {
|
||||||
|
if (myInfo) {
|
||||||
|
navigation.push('Tab-Shared-Account', { account })
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
navigationRef.navigate('Screen-ImagesViewer', {
|
||||||
|
imageUrls: [{ id: 'avatar', url: account.avatar }],
|
||||||
|
id: 'avatar',
|
||||||
|
hideCounter: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
|
@ -36,20 +45,6 @@ const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo, edit }) =>
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
uri={{ original: account?.avatar, static: account?.avatar_static }}
|
uri={{ original: account?.avatar, static: account?.avatar_static }}
|
||||||
/>
|
/>
|
||||||
{edit ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
alignContent: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type='icon' content='Edit' round onPress={() => {}} />
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||||
import Animated, {
|
import Animated, { Extrapolate, interpolate, useAnimatedStyle } from 'react-native-reanimated'
|
||||||
Extrapolate,
|
|
||||||
interpolate,
|
|
||||||
useAnimatedStyle
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -22,7 +18,7 @@ const AccountNav = React.memo(
|
||||||
const headerHeight = useSafeAreaInsets().top + 44
|
const headerHeight = useSafeAreaInsets().top + 44
|
||||||
|
|
||||||
const nameY =
|
const nameY =
|
||||||
Dimensions.get('screen').width / 3 +
|
Dimensions.get('window').width / 3 +
|
||||||
StyleConstants.Avatar.L -
|
StyleConstants.Avatar.L -
|
||||||
StyleConstants.Spacing.Global.PagePadding * 2 +
|
StyleConstants.Spacing.Global.PagePadding * 2 +
|
||||||
StyleConstants.Spacing.M -
|
StyleConstants.Spacing.M -
|
||||||
|
@ -35,12 +31,7 @@ const AccountNav = React.memo(
|
||||||
})
|
})
|
||||||
const styleMarginTop = useAnimatedStyle(() => {
|
const styleMarginTop = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
marginTop: interpolate(
|
marginTop: interpolate(scrollY.value, [nameY, nameY + 20], [50, 0], Extrapolate.CLAMP)
|
||||||
scrollY.value,
|
|
||||||
[nameY, nameY + 20],
|
|
||||||
[50, 0],
|
|
||||||
Extrapolate.CLAMP
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -61,8 +52,7 @@ const AccountNav = React.memo(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginTop:
|
marginTop: useSafeAreaInsets().top + (44 - StyleConstants.Font.Size.L) / 2
|
||||||
useSafeAreaInsets().top + (44 - StyleConstants.Font.Size.L) / 2
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Animated.View style={[{ flexDirection: 'row' }, styleMarginTop]}>
|
<Animated.View style={[{ flexDirection: 'row' }, styleMarginTop]}>
|
||||||
|
|
|
@ -5,8 +5,10 @@ import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { FlatList } from 'react-native'
|
import { FlatList, View } from 'react-native'
|
||||||
import { InfiniteQueryObserver, useQueryClient } from '@tanstack/react-query'
|
import { InfiniteQueryObserver, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import ComponentSeparator from '@components/Separator'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
|
||||||
const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
navigation,
|
navigation,
|
||||||
|
@ -98,94 +100,49 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
queryOptions={{ staleTime: 0, refetchOnMount: true }}
|
queryOptions={{ staleTime: 0, refetchOnMount: true }}
|
||||||
customProps={{
|
customProps={{
|
||||||
renderItem: ({ item }) => {
|
ItemSeparatorComponent: ({ leadingItem }) => {
|
||||||
|
const levels = {
|
||||||
|
current:
|
||||||
|
replyLevels.current.find(reply => reply.id === leadingItem.in_reply_to_id)?.level || 0
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
|
<ComponentSeparator
|
||||||
|
extraMarginLeft={
|
||||||
|
toot.id === leadingItem.id
|
||||||
|
? 0
|
||||||
|
: StyleConstants.Avatar.XS +
|
||||||
|
StyleConstants.Spacing.S +
|
||||||
|
Math.max(0, levels.current - 1) * 8
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderItem: ({ item, index }) => {
|
||||||
|
const levels = {
|
||||||
|
previous:
|
||||||
|
replyLevels.current.find(
|
||||||
|
reply => reply.id === data.current?.[index - 1]?.in_reply_to_id
|
||||||
|
)?.level || 0,
|
||||||
|
current:
|
||||||
|
replyLevels.current.find(reply => reply.id === item.in_reply_to_id)?.level || 0,
|
||||||
|
next:
|
||||||
|
replyLevels.current.find(
|
||||||
|
reply => reply.id === data.current?.[index + 1]?.in_reply_to_id
|
||||||
|
)?.level || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ marginLeft: Math.max(0, levels.current - 1) * StyleConstants.Spacing.S }}>
|
||||||
<TimelineDefault
|
<TimelineDefault
|
||||||
item={item}
|
item={item}
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
rootQueryKey={rootQueryKey}
|
rootQueryKey={rootQueryKey}
|
||||||
highlighted={toot.id === item.id}
|
highlighted={toot.id === item.id}
|
||||||
|
isConversation={toot.id !== item.id}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
// renderItem: ({ item, index }) => {
|
|
||||||
// const levels = {
|
|
||||||
// previous:
|
|
||||||
// replyLevels.current.find(
|
|
||||||
// reply => reply.id === data.current?.[index - 1]?.in_reply_to_id
|
|
||||||
// )?.level || 0,
|
|
||||||
// current:
|
|
||||||
// replyLevels.current.find(reply => reply.id === item.in_reply_to_id)?.level || 0,
|
|
||||||
// next:
|
|
||||||
// replyLevels.current.find(
|
|
||||||
// reply => reply.id === data.current?.[index + 1]?.in_reply_to_id
|
|
||||||
// )?.level || 0
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <>
|
|
||||||
// <TimelineDefault
|
|
||||||
// item={item}
|
|
||||||
// queryKey={queryKey}
|
|
||||||
// rootQueryKey={rootQueryKey}
|
|
||||||
// highlighted={toot.id === item.id}
|
|
||||||
// isConversation={toot.id !== item.id}
|
|
||||||
// />
|
|
||||||
// {Array.from(Array(levels.current)).map((_, i) => {
|
|
||||||
// if (index < highlightIndex.current) return null
|
|
||||||
// if (
|
|
||||||
// levels.previous + 1 === levels.current ||
|
|
||||||
// (levels.previous && levels.current && levels.previous === levels.current)
|
|
||||||
// ) {
|
|
||||||
// return (
|
|
||||||
// <View
|
|
||||||
// key={i}
|
|
||||||
// style={{
|
|
||||||
// position: 'absolute',
|
|
||||||
// top: 0,
|
|
||||||
// left: StyleConstants.Spacing.Global.PagePadding / 2 + 8 * i,
|
|
||||||
// height:
|
|
||||||
// levels.current === levels.next
|
|
||||||
// ? StyleConstants.Spacing.Global.PagePadding
|
|
||||||
// : StyleConstants.Spacing.Global.PagePadding + StyleConstants.Avatar.XS,
|
|
||||||
// borderLeftColor: colors.border,
|
|
||||||
// borderLeftWidth: 1
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// )
|
|
||||||
// } else {
|
|
||||||
// return null
|
|
||||||
// }
|
|
||||||
// })}
|
|
||||||
// {Array.from(Array(levels.next)).map((_, i) => {
|
|
||||||
// if (index < highlightIndex.current) return null
|
|
||||||
// if (
|
|
||||||
// levels.current + 1 === levels.next ||
|
|
||||||
// (levels.current && levels.next && levels.current === levels.next)
|
|
||||||
// ) {
|
|
||||||
// return (
|
|
||||||
// <View
|
|
||||||
// key={i}
|
|
||||||
// style={{
|
|
||||||
// position: 'absolute',
|
|
||||||
// top:
|
|
||||||
// levels.current + 1 === levels.next && levels.next > i + 1
|
|
||||||
// ? StyleConstants.Spacing.Global.PagePadding + StyleConstants.Avatar.XS
|
|
||||||
// : StyleConstants.Spacing.Global.PagePadding,
|
|
||||||
// left: StyleConstants.Spacing.Global.PagePadding / 2 + 8 * i,
|
|
||||||
// height: 200,
|
|
||||||
// borderLeftColor: colors.border,
|
|
||||||
// borderLeftWidth: 1
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// )
|
|
||||||
// } else {
|
|
||||||
// return null
|
|
||||||
// }
|
|
||||||
// })}
|
|
||||||
// </>
|
|
||||||
// )
|
|
||||||
// },
|
|
||||||
onScrollToIndexFailed: error => {
|
onScrollToIndexFailed: error => {
|
||||||
const offset = error.averageItemLength * error.index
|
const offset = error.averageItemLength * error.index
|
||||||
flRef.current?.scrollToOffset({ offset })
|
flRef.current?.scrollToOffset({ offset })
|
||||||
|
|
|
@ -19,7 +19,7 @@ export type InstanceV10 = {
|
||||||
}
|
}
|
||||||
version: string
|
version: string
|
||||||
configuration?: Mastodon.Instance['configuration']
|
configuration?: Mastodon.Instance['configuration']
|
||||||
filters: Mastodon.Filter[]
|
filters: Mastodon.Filter<any>[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
follow_request: boolean
|
follow_request: boolean
|
||||||
|
|
|
@ -18,7 +18,7 @@ export type InstanceV11 = {
|
||||||
}
|
}
|
||||||
version: string
|
version: string
|
||||||
configuration?: Mastodon.Instance['configuration']
|
configuration?: Mastodon.Instance['configuration']
|
||||||
filters: Mastodon.Filter[]
|
filters: Mastodon.Filter<any>[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
follow_request: boolean
|
follow_request: boolean
|
||||||
|
|
|
@ -17,7 +17,7 @@ type Instance = {
|
||||||
avatarStatic: Mastodon.Account['avatar_static']
|
avatarStatic: Mastodon.Account['avatar_static']
|
||||||
preferences: Mastodon.Preferences
|
preferences: Mastodon.Preferences
|
||||||
}
|
}
|
||||||
filters: Mastodon.Filter[]
|
filters: Mastodon.Filter<any>[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
favourite: boolean
|
favourite: boolean
|
||||||
|
|
|
@ -18,7 +18,7 @@ type Instance = {
|
||||||
}
|
}
|
||||||
max_toot_chars?: number // To be deprecated in v4
|
max_toot_chars?: number // To be deprecated in v4
|
||||||
configuration?: Mastodon.Instance['configuration']
|
configuration?: Mastodon.Instance['configuration']
|
||||||
filters: Mastodon.Filter[]
|
filters: Mastodon.Filter<any>[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
favourite: boolean
|
favourite: boolean
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Instance = {
|
||||||
}
|
}
|
||||||
max_toot_chars?: number // To be deprecated in v4
|
max_toot_chars?: number // To be deprecated in v4
|
||||||
configuration?: Mastodon.Instance['configuration']
|
configuration?: Mastodon.Instance['configuration']
|
||||||
filters: Mastodon.Filter[]
|
filters: Mastodon.Filter<any>[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
favourite: boolean
|
favourite: boolean
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Instance = {
|
||||||
}
|
}
|
||||||
max_toot_chars?: number // To be deprecated in v4
|
max_toot_chars?: number // To be deprecated in v4
|
||||||
configuration?: Mastodon.Instance['configuration']
|
configuration?: Mastodon.Instance['configuration']
|
||||||
filters: Mastodon.Filter[]
|
filters: Mastodon.Filter<any>[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
favourite: boolean
|
favourite: boolean
|
||||||
|
|
|
@ -19,7 +19,7 @@ export type InstanceV9 = {
|
||||||
}
|
}
|
||||||
version: string
|
version: string
|
||||||
configuration?: Mastodon.Instance['configuration']
|
configuration?: Mastodon.Instance['configuration']
|
||||||
filters: Mastodon.Filter[]
|
filters: Mastodon.Filter<any>[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
favourite: boolean
|
favourite: boolean
|
||||||
|
|
|
@ -51,14 +51,14 @@ export type RootStackParamList = {
|
||||||
'Screen-ImagesViewer': {
|
'Screen-ImagesViewer': {
|
||||||
imageUrls: {
|
imageUrls: {
|
||||||
id: Mastodon.Attachment['id']
|
id: Mastodon.Attachment['id']
|
||||||
preview_url: Mastodon.AttachmentImage['preview_url']
|
preview_url?: Mastodon.AttachmentImage['preview_url']
|
||||||
url: Mastodon.AttachmentImage['url']
|
url: Mastodon.AttachmentImage['url']
|
||||||
remote_url?: Mastodon.AttachmentImage['remote_url']
|
remote_url?: Mastodon.AttachmentImage['remote_url']
|
||||||
blurhash: Mastodon.AttachmentImage['blurhash']
|
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
}[]
|
}[]
|
||||||
id: Mastodon.Attachment['id']
|
id: Mastodon.Attachment['id']
|
||||||
|
hideCounter?: boolean
|
||||||
}
|
}
|
||||||
'Screen-AccountSelection': {
|
'Screen-AccountSelection': {
|
||||||
component?: () => JSX.Element | undefined
|
component?: () => JSX.Element | undefined
|
||||||
|
|
|
@ -35,7 +35,11 @@ const pushUseConnect = () => {
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
retry: 10,
|
retry: (failureCount, error) => {
|
||||||
|
if (error.status == 404) return false
|
||||||
|
|
||||||
|
return failureCount < 10
|
||||||
|
},
|
||||||
retryOnMount: false,
|
retryOnMount: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
|
|
|
@ -51,7 +51,7 @@ export type QueryKeyTimeline = [
|
||||||
|
|
||||||
const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyTimeline>) => {
|
const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyTimeline>) => {
|
||||||
const page = queryKey[1]
|
const page = queryKey[1]
|
||||||
let params: { [key: string]: string } = { ...pageParam, limit: 40 }
|
let params: { [key: string]: string } = { limit: 40, ...pageParam }
|
||||||
|
|
||||||
switch (page.page) {
|
switch (page.page) {
|
||||||
case 'Following':
|
case 'Following':
|
||||||
|
|
|
@ -52,7 +52,7 @@ const addInstance = createAsyncThunk(
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
})
|
})
|
||||||
|
|
||||||
const { body: filters } = await apiGeneral<Mastodon.Filter[]>({
|
const { body: filters } = await apiGeneral<Mastodon.Filter<any>[]>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
domain,
|
domain,
|
||||||
url: `api/v1/filters`,
|
url: `api/v1/filters`,
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
export const updateFilters = createAsyncThunk(
|
export const updateFilters = createAsyncThunk(
|
||||||
'instances/updateFilters',
|
'instances/updateFilters',
|
||||||
async (): Promise<Mastodon.Filter[]> => {
|
async (): Promise<Mastodon.Filter<any>[]> => {
|
||||||
return apiInstance<Mastodon.Filter[]>({
|
return apiInstance<Mastodon.Filter<any>[]>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `filters`
|
url: `filters`
|
||||||
}).then(res => res.body)
|
}).then(res => res.body)
|
||||||
|
|
|
@ -3,13 +3,13 @@ const Base = 4
|
||||||
export const StyleConstants = {
|
export const StyleConstants = {
|
||||||
Font: {
|
Font: {
|
||||||
Size: { S: 14, M: 16, L: 18 },
|
Size: { S: 14, M: 16, L: 18 },
|
||||||
LineHeight: { S: 20, M: 22, L: 28 },
|
LineHeight: { S: 18, M: 21, L: 26 },
|
||||||
Weight: { Normal: '400' as '400', Bold: '600' as '600' }
|
Weight: { Normal: '400' as '400', Bold: '600' as '600' }
|
||||||
},
|
},
|
||||||
FontStyle: {
|
FontStyle: {
|
||||||
S: { fontSize: 14, lineHeight: 20 },
|
S: { fontSize: 14, lineHeight: 18 },
|
||||||
M: { fontSize: 16, lineHeight: 22 },
|
M: { fontSize: 16, lineHeight: 21 },
|
||||||
L: { fontSize: 20, lineHeight: 28 }
|
L: { fontSize: 20, lineHeight: 26 }
|
||||||
},
|
},
|
||||||
|
|
||||||
Spacing: {
|
Spacing: {
|
||||||
|
|
|
@ -1,15 +1,3 @@
|
||||||
// import { Dimensions } from 'react-native'
|
const adaptiveScale = (size: number, factor: number = 0) => Math.round(size + size * (factor / 8))
|
||||||
|
|
||||||
// const { width } = Dimensions.get('screen')
|
|
||||||
|
|
||||||
// const guidelineBaseWidth = 375
|
|
||||||
// const guidelineBaseHeight = 667
|
|
||||||
|
|
||||||
// const scale = (size: number) => (width / guidelineBaseWidth) * size
|
|
||||||
// const verticalScale = (size: number) => (height / guidelineBaseHeight) * size
|
|
||||||
// const adaptiveScale = (size: number, factor: number = 0) =>
|
|
||||||
// size + (scale(size) - size) * factor
|
|
||||||
const adaptiveScale = (size: number, factor: number = 0) =>
|
|
||||||
size + size * (factor / 8)
|
|
||||||
|
|
||||||
export { adaptiveScale }
|
export { adaptiveScale }
|
||||||
|
|
Loading…
Reference in New Issue