From eeee40b49ab3fcae8103ad331ec45393ee17fb33 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Wed, 23 Dec 2020 01:31:11 +0100 Subject: [PATCH] Basic announcement done --- src/@types/mastodon.d.ts | 11 +- src/Index.tsx | 31 ++- src/screens/Shared/Announcement.tsx | 277 ++++++++++++++++++++++++ src/screens/Shared/sharedScreens.tsx | 15 +- src/utils/fetches/announcementsFetch.ts | 10 + src/utils/styles/layoutAnimation.ts | 6 + 6 files changed, 340 insertions(+), 10 deletions(-) create mode 100644 src/screens/Shared/Announcement.tsx create mode 100644 src/utils/fetches/announcementsFetch.ts create mode 100644 src/utils/styles/layoutAnimation.ts diff --git a/src/@types/mastodon.d.ts b/src/@types/mastodon.d.ts index eeb2416d..9021985c 100644 --- a/src/@types/mastodon.d.ts +++ b/src/@types/mastodon.d.ts @@ -34,18 +34,21 @@ declare namespace Mastodon { type Announcement = { // Base id: string - text: string - published: boolean + content: string all_day: boolean - created_at: string + published_at: string updated_at: string read: boolean - reactions: AnnouncementReaction[] // Others scheduled_at?: string starts_at?: string ends_at?: string + reactions?: AnnouncementReaction[] + mentions?: Mention[] + statuses?: Status[] + tags?: Tag[] + emojis?: Emoji[] } type AnnouncementReaction = { diff --git a/src/Index.tsx b/src/Index.tsx index 6b85bb18..74e184f0 100644 --- a/src/Index.tsx +++ b/src/Index.tsx @@ -1,9 +1,12 @@ import 'react-native-gesture-handler' import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' -import { NavigationContainer } from '@react-navigation/native' +import { + NavigationContainer, + NavigationContainerRef +} from '@react-navigation/native' import { enableScreens } from 'react-native-screens' -import React, { useEffect } from 'react' +import React, { useEffect, useRef } from 'react' import { StatusBar } from 'react-native' import Toast from 'react-native-toast-message' import { Feather } from '@expo/vector-icons' @@ -23,6 +26,9 @@ import { getLocalUrl, updateLocalAccountPreferences } from '@utils/slices/instancesSlice' +import { useQuery } from 'react-query' +import { announcementFetch } from './utils/fetches/announcementsFetch' +import client from './api/client' enableScreens() const Tab = createBottomTabNavigator() @@ -67,10 +73,29 @@ export const Index: React.FC = ({ localCorrupt }) => { } }, []) + const navigationRef = useRef(null) + useEffect(() => { + client({ + method: 'get', + instance: 'local', + url: `announcements` + }) + .then(({ body }: { body?: Mastodon.Announcement[] }) => { + if (body?.filter(announcement => !announcement.read).length) { + navigationRef.current?.navigate('Screen-Shared-Announcements') + } + }) + .catch(() => {}) + }, []) + return ( <> - + ({ diff --git a/src/screens/Shared/Announcement.tsx b/src/screens/Shared/Announcement.tsx new file mode 100644 index 00000000..f7e30476 --- /dev/null +++ b/src/screens/Shared/Announcement.tsx @@ -0,0 +1,277 @@ +import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs' +import client from '@root/api/client' +import { ButtonRow } from '@root/components/Button' +import ParseContent from '@root/components/ParseContent' +import { announcementFetch } from '@root/utils/fetches/announcementsFetch' +import { StyleConstants } from '@root/utils/styles/constants' +import { useTheme } from '@root/utils/styles/ThemeManager' +import { BlurView } from 'expo-blur' +import React, { useCallback, useEffect, useRef, useState } from 'react' +import { + Dimensions, + Image, + Pressable, + StyleSheet, + Text, + TextInput, + View +} from 'react-native' +import { FlatList, ScrollView } from 'react-native-gesture-handler' +import { SafeAreaView } from 'react-native-safe-area-context' +import { useMutation, useQuery } from 'react-query' + +const fireMutation = async ({ + announcementId, + type, + name, + me +}: { + announcementId: Mastodon.Announcement['id'] + type: 'reaction' | 'dismiss' + name?: Mastodon.AnnouncementReaction['name'] + me?: boolean +}) => { + switch (type) { + case 'reaction': + return client({ + method: me ? 'delete' : 'put', + instance: 'local', + url: `announcements/${announcementId}/reactions/${name}` + }) + case 'dismiss': + return client({ + method: 'post', + instance: 'local', + url: `announcements/${announcementId}/dismiss` + }) + } +} + +const ScreenSharedAnnouncements: React.FC = ({ navigation }) => { + const { mode, theme } = useTheme() + const bottomTabBarHeight = useBottomTabBarHeight() + const [index, setIndex] = useState(0) + const invisibleTextInputRef = useRef(null) + + const queryKey = ['Announcements'] + const { data, refetch } = useQuery(queryKey, announcementFetch, { + select: announcements => + announcements.filter(announcement => !announcement.read) + }) + const { mutate } = useMutation(fireMutation, { + onSettled: () => { + refetch() + } + }) + + useEffect(() => { + if (data?.length === 0) { + navigation.goBack() + } + }, [data]) + + const renderItem = useCallback( + ({ item, index }: { item: Mastodon.Announcement; index: number }) => ( + + navigation.goBack()} + /> + + + + + {item.reactions?.length ? ( + + {item.reactions?.map(reaction => ( + + mutate({ + announcementId: item.id, + type: 'reaction', + name: reaction.name, + me: reaction.me + }) + } + > + {reaction.url ? ( + + ) : ( + {reaction.name} + )} + {reaction.count ? ( + + {reaction.count} + + ) : null} + + ))} + {/* invisibleTextInputRef.current?.focus()} + > + + */} + + ) : null} + mutate({ type: 'dismiss', announcementId: item.id })} + style={styles.button} + /> + + + ), + [] + ) + + const onMomentumScrollEnd = useCallback( + ({ + nativeEvent: { + contentOffset: { x }, + layoutMeasurement: { width } + } + }) => { + setIndex(Math.floor(x / width)) + }, + [] + ) + + return ( + + + + + 公告 + + + + {data && data.length > 1 ? ( + <> + {data.map((d, i) => ( + + ))} + + ) : null} + + + ) +} + +const styles = StyleSheet.create({ + base: { + flex: 1 + }, + invisibleTextInput: { ...StyleSheet.absoluteFillObject }, + header: { + justifyContent: 'center', + alignItems: 'center' + }, + headerText: { + fontSize: StyleConstants.Font.Size.M * 1.5, + fontWeight: StyleConstants.Font.Weight.Bold + }, + announcementContainer: { + width: Dimensions.get('screen').width, + padding: StyleConstants.Spacing.Global.PagePadding, + justifyContent: 'center' + }, + pressable: { ...StyleSheet.absoluteFillObject }, + announcement: { + flexShrink: 1, + padding: StyleConstants.Spacing.Global.PagePadding, + marginTop: StyleConstants.Spacing.Global.PagePadding, + borderWidth: 1, + borderRadius: 6 + }, + scrollView: { + marginBottom: StyleConstants.Spacing.Global.PagePadding / 2 + }, + reactions: { flexDirection: 'row', flexWrap: 'wrap' }, + reaction: { + borderWidth: StyleSheet.hairlineWidth, + padding: StyleConstants.Spacing.Global.PagePadding / 2, + marginTop: StyleConstants.Spacing.Global.PagePadding / 2, + marginBottom: StyleConstants.Spacing.Global.PagePadding / 2, + marginRight: StyleConstants.Spacing.M, + borderRadius: 6, + flexDirection: 'row' + }, + reactionImage: { + width: StyleConstants.Font.LineHeight.M + 3, + height: StyleConstants.Font.LineHeight.M + }, + reactionText: { + fontSize: StyleConstants.Font.Size.M + }, + reactionCount: { + fontSize: StyleConstants.Font.Size.S, + marginLeft: StyleConstants.Spacing.S + }, + button: { + marginTop: StyleConstants.Spacing.Global.PagePadding / 2 + }, + indicators: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center' + }, + indicator: { + width: StyleConstants.Spacing.S, + height: StyleConstants.Spacing.S, + borderRadius: StyleConstants.Spacing.S, + borderWidth: 1 + } +}) + +export default ScreenSharedAnnouncements diff --git a/src/screens/Shared/sharedScreens.tsx b/src/screens/Shared/sharedScreens.tsx index a01f5767..da382162 100644 --- a/src/screens/Shared/sharedScreens.tsx +++ b/src/screens/Shared/sharedScreens.tsx @@ -1,13 +1,13 @@ -import React from 'react' - +import { HeaderLeft } from '@root/components/Header' import ScreenSharedAccount from '@screens/Shared/Account' +import ScreenSharedAnnouncements from '@screens/Shared/Announcement' import ScreenSharedHashtag from '@screens/Shared/Hashtag' import ScreenSharedToot from '@screens/Shared/Toot' import Compose from '@screens/Shared/Compose' import ComposeEditAttachment from '@screens/Shared/Compose/EditAttachment' import ScreenSharedSearch from '@screens/Shared/Search' +import React from 'react' import { useTranslation } from 'react-i18next' -import { HeaderLeft } from '@root/components/Header' const sharedScreens = (Stack: any) => { const { t } = useTranslation() @@ -78,6 +78,15 @@ const sharedScreens = (Stack: any) => { options={{ stackPresentation: 'modal' }} + />, + ] } diff --git a/src/utils/fetches/announcementsFetch.ts b/src/utils/fetches/announcementsFetch.ts new file mode 100644 index 00000000..68714358 --- /dev/null +++ b/src/utils/fetches/announcementsFetch.ts @@ -0,0 +1,10 @@ +import client from '@api/client' + +export const announcementFetch = async (): Promise => { + const res = await client({ + method: 'get', + instance: 'local', + url: `announcements` + }) + return Promise.resolve(res.body) +} diff --git a/src/utils/styles/layoutAnimation.ts b/src/utils/styles/layoutAnimation.ts new file mode 100644 index 00000000..310a4d02 --- /dev/null +++ b/src/utils/styles/layoutAnimation.ts @@ -0,0 +1,6 @@ +import { LayoutAnimation } from 'react-native' + +const layoutAnimation = () => + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) + +export default layoutAnimation