diff --git a/api/src/__tests__/processRawContent.ts b/api/src/__tests__/processRawContent.ts
index beebca59..fbf71c9d 100644
--- a/api/src/__tests__/processRawContent.ts
+++ b/api/src/__tests__/processRawContent.ts
@@ -2,6 +2,7 @@ import {
generateMarkdownContent,
getCompleteImageVideoUrls,
getEmojiImageUrls,
+ userActivityMarkdownContent,
} from '../helpers';
describe('getCompleteImageUrls return image urls from html tags', () => {
@@ -186,3 +187,50 @@ describe('generate emoji url from image tag', () => {
expect(getEmojiImageUrls(content3)).toEqual([]);
});
});
+
+describe('generate new content for user activity', () => {
+ it('it should return Content based input', () => {
+ const content = 'Hello\n who is this';
+ const content1 = 'Just want to test\n\n something';
+
+ expect(userActivityMarkdownContent(content)).toEqual(content);
+ expect(userActivityMarkdownContent(content1)).toEqual(content1);
+ });
+ it('it should replace content image', () => {
+ const contentImage =
+ 'Hello\n [exampleImage1]';
+ const contentImageSrc =
+ '
';
+
+ expect(userActivityMarkdownContent(contentImage)).toEqual(
+ 'Hello\n ',
+ );
+ expect(userActivityMarkdownContent(contentImageSrc)).toEqual(
+ '',
+ );
+ });
+ it('it should convert emoji', () => {
+ const emojiContent =
+ 'Hello\n
';
+
+ expect(userActivityMarkdownContent(emojiContent)).toEqual(
+ 'Hello\n ',
+ );
+ });
+ it('it should convert mention', () => {
+ const mentionContent =
+ 'Is this true? @marcello';
+
+ expect(userActivityMarkdownContent(mentionContent)).toEqual(
+ 'Is this true? @marcello',
+ );
+ });
+ it('it should convert Link', () => {
+ const mentionContent =
+ 'Hello';
+
+ expect(userActivityMarkdownContent(mentionContent)).toEqual(
+ '[Hello](https://www.google.com)',
+ );
+ });
+});
diff --git a/api/src/helpers/processRawContent.ts b/api/src/helpers/processRawContent.ts
index 7656dae1..385b2161 100644
--- a/api/src/helpers/processRawContent.ts
+++ b/api/src/helpers/processRawContent.ts
@@ -16,6 +16,9 @@ const emojiBBCodeRegex = /(?<=^|\s):\w+:(?:t\d+:)?/g;
const emojiImageTagRegex = //g;
const emojiTitleRegex = /title="([^"]+)"/g;
+const userActivityContentRegex =
+ /(?:
]*src(?:set)?="(.+?)"(?:[^>]*title="([^"]*)")?(?:[^>]*class="([^"]*)")?[^>]*>)|(?:]* href="((https?:)?\/\/[^ ]*\.(?:jpe?g|png|gif|heic|heif|mov|mp4|webm|avi|wmv|flv|webp))"([^>]*?)title="([^"]*)"\s*>(\[.*?\])?<\/a>)|(?:]* class="mention" href="\/u\/([^"]+)">@(.*?)<\/a>)|(?:]* href="([^"]+)"[^>]*>(.*?)<\/a>)/g;
+
function handleRegexResult(
result: RegExpMatchArray,
host: string,
@@ -166,3 +169,43 @@ export function getMention(
return handleRegexResult(result, host, mentionRegex);
}
}
+
+export function userActivityMarkdownContent(content: string) {
+ const markdown = content.replace(
+ userActivityContentRegex,
+ (
+ _,
+ imgSrc: string,
+ imgTitle: string,
+ imgClass: string,
+ aHref: string,
+ _https,
+ _dataHref,
+ aTitle: string,
+ _emptyMention,
+ _urlName,
+ nameMention,
+ linkHref,
+ linkText,
+ ) => {
+ let modifiedImageMarkdown = ``;
+
+ if (imgSrc) {
+ modifiedImageMarkdown = ``;
+ } else if (aHref) {
+ modifiedImageMarkdown = ``;
+ } else if (nameMention) {
+ modifiedImageMarkdown = `@${nameMention}`;
+ } else if (linkHref && linkText) {
+ modifiedImageMarkdown = `[${linkText}](${linkHref})`;
+ }
+
+ return modifiedImageMarkdown;
+ },
+ );
+ return markdown;
+}
diff --git a/api/src/resolvers/site/aboutQuery.ts b/api/src/resolvers/site/aboutQuery.ts
index aa76e836..5095d6a8 100644
--- a/api/src/resolvers/site/aboutQuery.ts
+++ b/api/src/resolvers/site/aboutQuery.ts
@@ -11,17 +11,27 @@ let aboutResolver: FieldResolver<'Query', 'about'> = async (
try {
let siteUrl = `/about.json`;
+ /**
+ * In here when use newest version discourse from 3.2.0.beta4-dev the name of field change into topics_count and posts_count
+ *
+ * And for the previous version discourse it use topic_count and post_count
+ */
let {
data: {
about: {
- stats: { topic_count: topicCount, post_count: postCount },
+ stats: {
+ topics_count: topicsCount,
+ topic_count: topicCount,
+ posts_count: postsCount,
+ post_count: postCount,
+ },
},
},
} = await context.client.get(siteUrl);
return {
- topicCount,
- postCount,
+ topicCount: topicCount || topicsCount,
+ postCount: postCount || postsCount,
};
} catch (error) {
throw errorHandler(error);
diff --git a/api/src/typeSchemas/UserActions.ts b/api/src/typeSchemas/UserActions.ts
index a3aea65c..fde81a05 100644
--- a/api/src/typeSchemas/UserActions.ts
+++ b/api/src/typeSchemas/UserActions.ts
@@ -1,6 +1,7 @@
import { objectType } from 'nexus';
import { getNormalizedUrlTemplate } from '../resolvers/utils';
+import { userActivityMarkdownContent } from '../helpers';
export let UserActions = objectType({
name: 'UserActions',
@@ -37,5 +38,10 @@ export let UserActions = objectType({
t.int('topicId');
t.int('userId');
t.string('username');
+ t.nullable.string('markdownContent', {
+ resolve: ({ excerpt }) => {
+ return userActivityMarkdownContent(excerpt);
+ },
+ });
},
});
diff --git a/frontend/src/components/CustomFlatList/CustomFlatList.tsx b/frontend/src/components/CustomFlatList/CustomFlatList.tsx
index df745aeb..4e8820fe 100644
--- a/frontend/src/components/CustomFlatList/CustomFlatList.tsx
+++ b/frontend/src/components/CustomFlatList/CustomFlatList.tsx
@@ -219,6 +219,7 @@ function BaseCustomFlatList(
}
onEndReached={(info) => {
diff --git a/frontend/src/components/LoadingOrError.tsx b/frontend/src/components/LoadingOrError.tsx
index 6cf8d121..968e7a6b 100644
--- a/frontend/src/components/LoadingOrError.tsx
+++ b/frontend/src/components/LoadingOrError.tsx
@@ -1,17 +1,27 @@
import React, { useEffect } from 'react';
-import { View } from 'react-native';
+import {
+ StyleProp,
+ ScrollView,
+ View,
+ ViewStyle,
+ RefreshControl,
+ RefreshControlProps,
+} from 'react-native';
+
import { useNavigation } from '@react-navigation/native';
import { ActivityIndicator, Text } from '../core-ui';
import { showLogoutAlert } from '../helpers';
-import { makeStyles } from '../theme';
+import { makeStyles, useTheme } from '../theme';
import { StackNavProp } from '../types';
import { useAuth } from '../utils/AuthProvider';
import { ERROR_NOT_FOUND } from '../constants';
-type Props = {
+type Props = Pick & {
message?: string;
loading?: boolean;
+ style?: StyleProp;
+ refreshing?: boolean;
};
export function LoadingOrError(props: Props) {
@@ -36,15 +46,37 @@ export function LoadingOrError(props: Props) {
export function LoadingOrErrorView(props: Props) {
const styles = useStyles();
+ const { colors } = useTheme();
const {
loading = false,
message = loading
? t('Loading...')
: t('Something unexpected happened. Please try again'),
+ onRefresh,
+ style,
+ refreshing,
+ progressViewOffset,
} = props;
- return (
-
+ return onRefresh ? (
+
+ }
+ >
+
+ {loading ? : null}
+ {message}
+
+
+ ) : (
+
{loading ? : null}
{message}
@@ -58,4 +90,5 @@ const useStyles = makeStyles(() => ({
justifyContent: 'center',
width: '100%',
},
+ scrollViewContentStyle: { flex: 1 },
}));
diff --git a/frontend/src/components/Metrics/Metrics.tsx b/frontend/src/components/Metrics/Metrics.tsx
index 9110d02c..721deace 100644
--- a/frontend/src/components/Metrics/Metrics.tsx
+++ b/frontend/src/components/Metrics/Metrics.tsx
@@ -19,7 +19,7 @@ type Props = {
export { Props as MetricsProp };
-const DEBOUNCE_WAIT_TIME = 500;
+const DEBOUNCE_WAIT_TIME = 1000;
export function Metrics(props: Props) {
const { likedTopics } = useOngoingLikedTopic();
@@ -136,19 +136,21 @@ export function Metrics(props: Props) {
}, [performDebouncedLike]);
// TODO: Add navigation #800
- const [like] = useLikeTopicOrPost();
+ const [like, { loading }] = useLikeTopicOrPost();
const onPressLike = useCallback(() => {
- setLikeData(({ liked: prevLiked, likeCount: previousCount }) => {
- const liked = !prevLiked;
- const likeCount = getUpdatedLikeCount({
- liked,
- previousCount,
+ if (!loading) {
+ setLikeData(({ liked: prevLiked, likeCount: previousCount }) => {
+ const liked = !prevLiked;
+ const likeCount = getUpdatedLikeCount({
+ liked,
+ previousCount,
+ });
+ performDebouncedLike(liked);
+ return { liked, likeCount };
});
- performDebouncedLike(liked);
- return { liked, likeCount };
- });
- }, [performDebouncedLike]);
+ }
+ }, [loading, performDebouncedLike]);
return (
- {content}
+ {unescapeHTML(content)}
)}
>
diff --git a/frontend/src/components/PostItem/UserInformationPostItem.tsx b/frontend/src/components/PostItem/UserInformationPostItem.tsx
index 1ff90684..b0dda0f5 100644
--- a/frontend/src/components/PostItem/UserInformationPostItem.tsx
+++ b/frontend/src/components/PostItem/UserInformationPostItem.tsx
@@ -46,12 +46,13 @@ function BaseUserInformationPostItem(props: Props) {
let {
title,
- excerpt: content,
+ excerpt,
avatarTemplate,
categoryId,
hidden,
createdAt,
username,
+ markdownContent: content,
} = cacheUserAction;
const channels = storage.getItem('channels');
@@ -68,7 +69,7 @@ function BaseUserInformationPostItem(props: Props) {
topicId={topicId}
title={title}
currentUser={currentUser}
- content={content}
+ content={content || excerpt}
avatar={avatar}
channel={channel}
hidden={!!hidden}
diff --git a/frontend/src/components/PostList.tsx b/frontend/src/components/PostList.tsx
index 158935f3..fc56dc05 100644
--- a/frontend/src/components/PostList.tsx
+++ b/frontend/src/components/PostList.tsx
@@ -54,7 +54,7 @@ function PostList(props: Props) {
}
diff --git a/frontend/src/constants/theme/colors.ts b/frontend/src/constants/theme/colors.ts
index 89d4961f..c5ae441f 100644
--- a/frontend/src/constants/theme/colors.ts
+++ b/frontend/src/constants/theme/colors.ts
@@ -28,6 +28,10 @@ export const BASE_COLORS = {
lightGray: '#F2F2F2',
mustardYellow: '#CC9619',
lightYellow: '#FAF4E7',
+
+ squidInk: '#262A31',
+ approxBlackRussian: '#1C1F24',
+ lightSilver: '#D8D8D8',
};
export const FUNCTIONAL_COLORS = {
@@ -64,8 +68,11 @@ export const FUNCTIONAL_COLORS = {
pureWhite: BASE_COLORS.pureWhite,
pureBlack: BASE_COLORS.pureBlack,
- skeletonLoadingBackGround: BASE_COLORS.darkerGray,
- skeletonLoadingHighlight: BASE_COLORS.lightGray,
+ skeletonLoadingLightBackGround: BASE_COLORS.grey,
+ skeletonLoadingLightHighlight: BASE_COLORS.lightSilver,
+
+ skeletonLoadingDarkBackGround: BASE_COLORS.squidInk,
+ skeletonLoadingDarkHighlight: BASE_COLORS.approxBlackRussian,
yellowText: BASE_COLORS.mustardYellow,
};
diff --git a/frontend/src/core-ui/ActivityIndicator.tsx b/frontend/src/core-ui/ActivityIndicator.tsx
index b64eb82b..e8ce1acb 100644
--- a/frontend/src/core-ui/ActivityIndicator.tsx
+++ b/frontend/src/core-ui/ActivityIndicator.tsx
@@ -13,7 +13,7 @@ type Props = ActivityIndicatorProps & {
export function ActivityIndicator(props: Props) {
const { colors } = useTheme();
- const { color = 'primary', ...otherProps } = props;
+ const { color = 'loading', ...otherProps } = props;
const indicatorColor = colors[color];
return ;
diff --git a/frontend/src/graphql/server/userActivity.ts b/frontend/src/graphql/server/userActivity.ts
index f56a15c4..e2767b94 100644
--- a/frontend/src/graphql/server/userActivity.ts
+++ b/frontend/src/graphql/server/userActivity.ts
@@ -13,6 +13,7 @@ export const USER_ACTIONS_FRAGMENT = gql`
topicId
username
hidden
+ markdownContent
}
`;
diff --git a/frontend/src/helpers/__tests__/unescapeHTML.test.ts b/frontend/src/helpers/__tests__/unescapeHTML.test.ts
new file mode 100644
index 00000000..8790d260
--- /dev/null
+++ b/frontend/src/helpers/__tests__/unescapeHTML.test.ts
@@ -0,0 +1,9 @@
+import { unescapeHTML } from '../unescapeHTML';
+
+it('should unescape HTML characters', () => {
+ expect(
+ unescapeHTML(
+ `<test>'hello' & "world"…</test>`,
+ ),
+ ).toEqual(`'hello' & "world"...`);
+});
diff --git a/frontend/src/helpers/index.ts b/frontend/src/helpers/index.ts
index d7cd335e..3ecceb2f 100644
--- a/frontend/src/helpers/index.ts
+++ b/frontend/src/helpers/index.ts
@@ -46,3 +46,4 @@ export * from './checkImageFile';
export * from './PrivateTopicAlert';
export * from './PushNotificationsSetupFailAlert';
export * from './parser';
+export * from './unescapeHTML';
diff --git a/frontend/src/helpers/localStorage.tsx b/frontend/src/helpers/localStorage.tsx
index e1166d1c..13a9567b 100644
--- a/frontend/src/helpers/localStorage.tsx
+++ b/frontend/src/helpers/localStorage.tsx
@@ -23,6 +23,8 @@ export let PushNotificationsPreferences = Record({
shouldSetBadge: Boolean,
});
+export let SelectedHomeChannelId = Number;
+
let [StorageProvider, useStorage] = createCachedStorage(
{
colorScheme: (value) => ColorScheme.check(value),
@@ -31,6 +33,7 @@ let [StorageProvider, useStorage] = createCachedStorage(
expoPushToken: (value) => String.check(value),
channels: (value) => ChannelList.check(value),
pushNotifications: (value) => PushNotificationsPreferences.check(value),
+ homeChannelId: (value) => SelectedHomeChannelId.check(value),
},
'@Cached/',
);
diff --git a/frontend/src/helpers/unescapeHTML.ts b/frontend/src/helpers/unescapeHTML.ts
new file mode 100644
index 00000000..801f7ef3
--- /dev/null
+++ b/frontend/src/helpers/unescapeHTML.ts
@@ -0,0 +1,15 @@
+const htmlEntities: Record = {
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ ''': "'",
+ '&': '&',
+ '…': '...',
+};
+
+export function unescapeHTML(str: string) {
+ return str.replace(
+ /<|>|"|'|&|…/g,
+ (match: string) => htmlEntities[match],
+ );
+}
diff --git a/frontend/src/hooks/post/useLikeTopicOrPost.ts b/frontend/src/hooks/post/useLikeTopicOrPost.ts
index fd59811c..1ba898eb 100644
--- a/frontend/src/hooks/post/useLikeTopicOrPost.ts
+++ b/frontend/src/hooks/post/useLikeTopicOrPost.ts
@@ -109,7 +109,10 @@ const optimisticResponse: MutationOptimisticResponse = ({
const refetchQueries: MutationRefetchQueries = ({ data }) => {
const topicDetailQuery = {
query: GetTopicDetailDocument,
- variables: { topicId: data?.likeTopicOrPost.topicId },
+ variables: {
+ topicId: data?.likeTopicOrPost.topicId,
+ includeFirstPost: true,
+ },
};
return [TOPICS, topicDetailQuery];
};
diff --git a/frontend/src/hooks/post/useTopicList.ts b/frontend/src/hooks/post/useTopicList.ts
index 210032e1..b29d1d9d 100644
--- a/frontend/src/hooks/post/useTopicList.ts
+++ b/frontend/src/hooks/post/useTopicList.ts
@@ -21,13 +21,11 @@ export function useTopicList(
export function useLazyTopicList(
options?: LazyQueryHookOptions,
) {
- const [getTopicList, { loading, error, refetch, fetchMore }] = useLazyQuery<
- TopicListType,
- TopicListVariables
- >(TopicsDocument, {
- context: { queryDeduplication: true },
- ...options,
- });
+ const [getTopicList, { data, loading, error, refetch, fetchMore }] =
+ useLazyQuery(TopicsDocument, {
+ context: { queryDeduplication: true },
+ ...options,
+ });
- return { getTopicList, loading, error, refetch, fetchMore };
+ return { getTopicList, loading, error, refetch, fetchMore, data };
}
diff --git a/frontend/src/hooks/site/useChannels.ts b/frontend/src/hooks/site/useChannels.ts
index f6aaab0b..39e58dba 100644
--- a/frontend/src/hooks/site/useChannels.ts
+++ b/frontend/src/hooks/site/useChannels.ts
@@ -11,7 +11,7 @@ export function useChannels(
options?: QueryHookOptions,
errorAlert: ErrorAlertOptionType = 'SHOW_ALERT',
) {
- const { data, loading, error } = useQuery(
+ const { data, loading, error, refetch } = useQuery(
GetChannelsDocument,
{
...options,
@@ -19,5 +19,5 @@ export function useChannels(
errorAlert,
);
- return { data, loading, error };
+ return { data, loading, error, refetch };
}
diff --git a/frontend/src/hooks/useUpdateApp.ts b/frontend/src/hooks/useUpdateApp.ts
new file mode 100644
index 00000000..a362676c
--- /dev/null
+++ b/frontend/src/hooks/useUpdateApp.ts
@@ -0,0 +1,39 @@
+import { useEffect, useState } from 'react';
+import * as Updates from 'expo-updates';
+
+export function useUpdateApp() {
+ const [loading, setLoading] = useState(false);
+
+ const checkUpdate = async () => {
+ const { isAvailable } = await Updates.checkForUpdateAsync();
+ if (isAvailable) {
+ setLoading(true);
+ performUpdate();
+ }
+ };
+
+ const performUpdate = async () => {
+ Updates.fetchUpdateAsync()
+ .then(() => {
+ reloadApp();
+ })
+ .catch(() => {
+ reloadApp();
+ });
+ };
+
+ const reloadApp = async () => {
+ await Updates.reloadAsync();
+ };
+
+ useEffect(() => {
+ if (!__DEV__) {
+ checkUpdate();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return {
+ loading,
+ };
+}
diff --git a/frontend/src/navigation/AppNavigator.tsx b/frontend/src/navigation/AppNavigator.tsx
index e8218b44..6c2ddf8e 100644
--- a/frontend/src/navigation/AppNavigator.tsx
+++ b/frontend/src/navigation/AppNavigator.tsx
@@ -9,8 +9,9 @@ import {
import { StatusBar } from 'expo-status-bar';
import * as Linking from 'expo-linking';
import * as Notifications from 'expo-notifications';
+import { View } from 'react-native';
-import { useColorScheme } from '../theme';
+import { makeStyles, useColorScheme } from '../theme';
import { RootStackParamList } from '../types';
import {
DEEP_LINK_SCREEN_CONFIG,
@@ -23,34 +24,41 @@ import { isRouteBesidePost, postOrMessageDetailPathToRoutes } from '../helpers';
import { useRedirect } from '../utils';
import { useInitialLoad } from '../hooks/useInitialLoad';
import { LoadingOrErrorView } from '../components';
+import { useUpdateApp } from '../hooks/useUpdateApp';
import RootStackNavigator from './RootStackNavigator';
import { navigationRef } from './NavigationService';
+import { useAuth } from '../utils/AuthProvider';
export default function AppNavigator() {
const { colorScheme } = useColorScheme();
const useInitialLoadResult = useInitialLoad();
const { setRedirectPath } = useRedirect();
+ const auth = useAuth();
+ const styles = useStyles();
+ const { loading: appUpdateLoading } = useUpdateApp();
const darkMode = colorScheme === 'dark';
return (
<>
- {useInitialLoadResult.loading ? (
-
+ {useInitialLoadResult.loading || auth.isLoading || appUpdateLoading ? (
+
) : (
-
-
-
+
+
+
+
+
)}
>
);
@@ -123,3 +131,10 @@ const createLinkingConfig = (params: CreateLinkingConfigParams) => {
};
return linking;
};
+
+const useStyles = makeStyles(({ colors }) => ({
+ background: {
+ flex: 1,
+ backgroundColor: colors.background,
+ },
+}));
diff --git a/frontend/src/screens/Activity.tsx b/frontend/src/screens/Activity.tsx
index 290a24cc..54d820b3 100644
--- a/frontend/src/screens/Activity.tsx
+++ b/frontend/src/screens/Activity.tsx
@@ -39,7 +39,7 @@ export default function Activity() {
}
if (loading && activities.length < 1) {
- return ;
+ return ;
}
let content;
@@ -80,7 +80,7 @@ export default function Activity() {
return {content};
}
-const useStyles = makeStyles(({ spacing }) => ({
+const useStyles = makeStyles(({ spacing, colors }) => ({
contentContainer: {
paddingTop: spacing.m,
},
@@ -98,4 +98,7 @@ const useStyles = makeStyles(({ spacing }) => ({
noActivityText: {
alignSelf: 'center',
},
+ loadingContainer: {
+ backgroundColor: colors.background,
+ },
}));
diff --git a/frontend/src/screens/Channels/Channels.tsx b/frontend/src/screens/Channels/Channels.tsx
index ea84652e..4e1ace59 100644
--- a/frontend/src/screens/Channels/Channels.tsx
+++ b/frontend/src/screens/Channels/Channels.tsx
@@ -4,7 +4,11 @@ import { useNavigation, useRoute } from '@react-navigation/native';
import { useFormContext } from 'react-hook-form';
import { CustomHeader, HeaderItem, ModalHeader } from '../../components';
-import { isNoChannelFilter, NO_CHANNEL_FILTER } from '../../constants';
+import {
+ isNoChannelFilter,
+ NO_CHANNEL_FILTER,
+ NO_CHANNEL_FILTER_ID,
+} from '../../constants';
import { useStorage } from '../../helpers';
import { makeStyles } from '../../theme';
import { RootStackNavProp, RootStackRouteProp } from '../../types';
@@ -25,13 +29,19 @@ export default function Channels() {
const { setValue, getValues } = useFormContext();
const { channelId: selectedChannelId } = getValues();
+ const homeSelectedChannelId = storage.getItem('homeChannelId');
+
+ const selectedChannel =
+ prevScreen === 'Home' ? homeSelectedChannelId : selectedChannelId;
+
const ios = Platform.OS === 'ios';
const onPress = (id: number) => {
- setValue('channelId', id);
if (prevScreen === 'Home') {
+ storage.setItem('homeChannelId', id);
navigate('TabNav', { screen: 'Home' });
} else {
+ setValue('channelId', id, { shouldDirty: true });
navigate(prevScreen);
}
};
@@ -49,7 +59,9 @@ export default function Channels() {
{prevScreen === 'Home' && (
onPress(NO_CHANNEL_FILTER.id)}
/>
@@ -59,7 +71,7 @@ export default function Channels() {
return (
onPress(id)}
/>
diff --git a/frontend/src/screens/EmailAddress/EmailAddress.tsx b/frontend/src/screens/EmailAddress/EmailAddress.tsx
index 5ab28161..1365a742 100644
--- a/frontend/src/screens/EmailAddress/EmailAddress.tsx
+++ b/frontend/src/screens/EmailAddress/EmailAddress.tsx
@@ -66,7 +66,7 @@ export default function EmailAddress() {
);
if (userLoading && emailAddress.length === 0) {
- return ;
+ return ;
}
return (
@@ -109,4 +109,7 @@ const useStyles = makeStyles(({ colors, spacing }) => ({
right: 0,
bottom: 0,
},
+ loadingContainer: {
+ backgroundColor: colors.background,
+ },
}));
diff --git a/frontend/src/screens/Home/Home.tsx b/frontend/src/screens/Home/Home.tsx
index 6aa44966..9d9dd73f 100644
--- a/frontend/src/screens/Home/Home.tsx
+++ b/frontend/src/screens/Home/Home.tsx
@@ -15,7 +15,6 @@ import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
-import { useFormContext } from 'react-hook-form';
import {
FooterLoadingIndicator,
@@ -34,7 +33,6 @@ import {
TopicsSortEnum,
TopicsQuery,
TopicsQueryVariables,
- TopicsDocument,
TopicFragmentDoc,
TopicFragment,
} from '../../generated/server';
@@ -106,9 +104,6 @@ export default function Home() {
const { addListener, navigate } = useNavigation>();
const { params } = useRoute>();
- const { getValues } = useFormContext();
- const { channelId: receivedChannelId } = getValues();
-
const routeParams = params === undefined ? false : params.backToTop;
const FIRST_PAGE = 0;
@@ -174,7 +169,11 @@ export default function Home() {
const [allTopicCount, setAllTopicCount] = useState(0);
const [width, setWidth] = useState(0);
- const { loading: channelsLoading, error: channelsError } = useChannels(
+ const {
+ loading: channelsLoading,
+ error: channelsError,
+ refetch: channelsRefetch,
+ } = useChannels(
{
onCompleted: (data) => {
if (data && data.category.categories) {
@@ -185,6 +184,10 @@ export default function Home() {
storage.setItem('channels', channels);
}
},
+ onError: () => {
+ setRefreshing(false);
+ setLoading(false);
+ },
},
'HIDE_ALERT',
);
@@ -230,6 +233,7 @@ export default function Home() {
error: topicsError,
refetch: refetchTopics,
fetchMore: fetchMoreTopics,
+ data: topicDataList,
} = useLazyTopicList({
variables: isNoChannelFilter(selectedChannelId)
? { sort: sortState, page, username }
@@ -238,30 +242,34 @@ export default function Home() {
setRefreshing(false);
setLoading(false);
},
- onCompleted: (data) => {
- if (data) {
- setData(data);
- setLoading(false);
- }
+ onCompleted: () => {
+ setLoading(false);
},
});
const getData = useCallback(
(variables: TopicsQueryVariables) => {
- try {
- const data: TopicsQuery | null = client.readQuery({
- query: TopicsDocument,
- variables,
- });
- data && setData(data);
- } catch (e) {
- setLoading(true);
- }
getTopicList({ variables });
},
- [getTopicList, setData],
+ [getTopicList],
);
+ useEffect(() => {
+ /**
+ * This useEffect is used to set data if there are changes in the result query.
+ * If the topics list is cached, it will show the result in `topicDataList`.
+ * But if the topics list is not in the cache, it will show `undefined` until it finishes getting data from the query.
+ *
+ */
+
+ if (topicDataList) {
+ setData(topicDataList);
+ setLoading(false);
+ } else {
+ setLoading(true);
+ }
+ }, [setData, topicDataList]);
+
useLayoutEffect(() => {
if (!isFlatList(postListRef.current)) {
return;
@@ -273,18 +281,27 @@ export default function Home() {
}, [selectedChannelId]);
useEffect(() => {
- let channels = storage.getItem('channels');
- if (channels && receivedChannelId) {
- setSelectedChannelId(receivedChannelId);
- } else if (channels) {
- setSelectedChannelId(NO_CHANNEL_FILTER.id);
- }
-
const unsubscribe = addListener('focus', () => {
+ /**
+ * We need to call `getHomeChannelId` to retrieve the initial value because this function will only be called once during the first render.
+ *
+ * During the first render, the value of `receivedChannelId` has not changed outside of the `useEffect`. This can result in the function using the previously selected channel's value.
+ *
+ * In the previous code, we utilized param screen or `watch` from `react-hook-form`, ensuring that the value had already changed during the initial render or before calling this function.
+ */
+
+ const receivedChannelId = storage.getItem('homeChannelId');
+ let channels = storage.getItem('channels');
+ if (channels && receivedChannelId) {
+ setSelectedChannelId(receivedChannelId);
+ } else if (channels) {
+ setSelectedChannelId(NO_CHANNEL_FILTER.id);
+ }
+
let categoryId = receivedChannelId;
if (receivedChannelId) {
categoryId = isNoChannelFilter(receivedChannelId)
- ? undefined
+ ? null
: receivedChannelId;
}
let currentPage = page;
@@ -308,7 +325,6 @@ export default function Home() {
}, [
selectedChannelId,
getAbout,
- receivedChannelId,
username,
storage,
sortState,
@@ -346,10 +362,15 @@ export default function Home() {
setRefreshing(true);
if (refetchTopics) {
setPage(FIRST_PAGE);
- refetchTopics().then(() => setRefreshing(false));
+ refetchTopics().finally(() => setRefreshing(false));
}
};
+ const onRefreshError = () => {
+ channelsRefetch();
+ onRefresh();
+ };
+
const onSegmentedControlItemPress = ({ name }: SortOption) => {
const sortState: TopicsSortEnum =
name === 'LATEST' ? TopicsSortEnum.Latest : TopicsSortEnum.Top;
@@ -442,10 +463,27 @@ export default function Home() {
const content = () => {
if (channelsError) {
- return ;
+ return (
+ {
+ channelsRefetch();
+ onRefresh();
+ }}
+ />
+ );
}
if (topicsError) {
- return ;
+ return (
+
+ );
}
if (!topicsData || channelsLoading || loading) {
return ;
@@ -527,7 +565,6 @@ const useStyles = makeStyles(({ colors, shadow, spacing }) => ({
container: {
flex: 1,
width: '100%',
- alignItems: 'flex-start',
justifyContent: 'flex-start',
backgroundColor: colors.backgroundDarker,
},
diff --git a/frontend/src/screens/InstanceLoading.tsx b/frontend/src/screens/InstanceLoading.tsx
index 6a11d1b2..b7c081dc 100644
--- a/frontend/src/screens/InstanceLoading.tsx
+++ b/frontend/src/screens/InstanceLoading.tsx
@@ -1,9 +1,11 @@
import React, { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
+import { View } from 'react-native';
import { LoadingOrError } from '../components';
import { StackNavProp } from '../types';
import { useSiteSettings } from '../hooks';
+import { makeStyles } from '../theme';
export default function Loading() {
const { reset } = useNavigation>();
@@ -12,6 +14,7 @@ export default function Loading() {
canSignUp,
error: siteSettingsError,
} = useSiteSettings({ fetchPolicy: 'network-only' });
+ const styles = useStyles();
useEffect(() => {
if (loading) {
@@ -31,5 +34,16 @@ export default function Loading() {
}
}, [canSignUp, siteSettingsError, loading, reset]);
- return <>{loading && }>;
+ return (
+
+ {loading && }
+
+ );
}
+
+const useStyles = makeStyles(({ colors }) => ({
+ background: {
+ flex: 1,
+ backgroundColor: colors.background,
+ },
+}));
diff --git a/frontend/src/screens/MessageDetail/MessageDetail.tsx b/frontend/src/screens/MessageDetail/MessageDetail.tsx
index 917adaf5..304cf0ab 100644
--- a/frontend/src/screens/MessageDetail/MessageDetail.tsx
+++ b/frontend/src/screens/MessageDetail/MessageDetail.tsx
@@ -596,7 +596,7 @@ export default function MessageDetail() {
loadMoreMessages(false)}
- tintColor={colors.primary}
+ tintColor={colors.loading}
/>
}
data={data?.contents ?? []}
diff --git a/frontend/src/screens/Messages/Messages.tsx b/frontend/src/screens/Messages/Messages.tsx
index f36c6790..e12c1787 100644
--- a/frontend/src/screens/Messages/Messages.tsx
+++ b/frontend/src/screens/Messages/Messages.tsx
@@ -167,7 +167,7 @@ export default function Messages() {
}
getItem={getItem}
diff --git a/frontend/src/screens/Notifications/Notifications.tsx b/frontend/src/screens/Notifications/Notifications.tsx
index 4e62f5e7..6693baeb 100644
--- a/frontend/src/screens/Notifications/Notifications.tsx
+++ b/frontend/src/screens/Notifications/Notifications.tsx
@@ -236,7 +236,7 @@ export default function Notifications() {
}
getItem={getItem}
diff --git a/frontend/src/screens/PostDetail/PostDetail.tsx b/frontend/src/screens/PostDetail/PostDetail.tsx
index 7d77b197..77fa15d7 100644
--- a/frontend/src/screens/PostDetail/PostDetail.tsx
+++ b/frontend/src/screens/PostDetail/PostDetail.tsx
@@ -175,7 +175,7 @@ export default function PostDetail() {
const showOptions =
false ||
- !!(topic && topic.canEditTopic) ||
+ !!(firstPost && firstPost.canEdit) ||
(!!firstPost && firstPost.canFlag && !firstPost.hidden);
useEffect(() => {
@@ -373,7 +373,7 @@ export default function PostDetail() {
let {
id,
canFlag = !!(firstPost && firstPost.canFlag),
- canEdit = !!(topic && topic.canEditTopic),
+ canEdit = !!(firstPost && firstPost.canEdit),
flaggedByCommunity = !!(firstPost && firstPost.hidden),
fromPost = true,
author,
@@ -551,7 +551,7 @@ export default function PostDetail() {
refreshing={
(loadingRefresh || isLoadingOlderPost) && !isLoadingNewerPost
}
- refreshControlTintColor={colors.primary}
+ refreshControlTintColor={colors.loading}
onEndReachedThreshold={0.1}
onEndReached={() => loadMoreComments(true)}
ListFooterComponent={
diff --git a/frontend/src/screens/PostDetail/PostDetailSkeletonLoading.tsx b/frontend/src/screens/PostDetail/PostDetailSkeletonLoading.tsx
index 9f38cae1..caa4d7d9 100644
--- a/frontend/src/screens/PostDetail/PostDetailSkeletonLoading.tsx
+++ b/frontend/src/screens/PostDetail/PostDetailSkeletonLoading.tsx
@@ -16,8 +16,8 @@ export default function (props: Props) {
diff --git a/frontend/src/screens/SelectUser/SelectUser.tsx b/frontend/src/screens/SelectUser/SelectUser.tsx
index 56ecd34d..73981f6c 100644
--- a/frontend/src/screens/SelectUser/SelectUser.tsx
+++ b/frontend/src/screens/SelectUser/SelectUser.tsx
@@ -182,7 +182,12 @@ export default function SelectUser() {
user.username
.toLowerCase()
.includes(searchValue.toLowerCase()) &&
- !selectedUsers.includes({ ...user, name: user.name ?? null }),
+ !selectedUsers.some(
+ (selectedUser) =>
+ selectedUser.name === user.name &&
+ selectedUser.username === user.username &&
+ user.avatar === selectedUser.avatar,
+ ),
)
.map((user) => {
if (user.name && user.name !== ownerName) {
diff --git a/frontend/src/theme/theme.ts b/frontend/src/theme/theme.ts
index 128e2f43..87512ae6 100644
--- a/frontend/src/theme/theme.ts
+++ b/frontend/src/theme/theme.ts
@@ -55,6 +55,15 @@ function colorTheme(isDarkMode: boolean) {
toastInfoText: isDarkMode
? FUNCTIONAL_COLORS.darkTextLighter
: FUNCTIONAL_COLORS.lightTextDarker,
+ skeletonLoadingBackgroundMode: isDarkMode
+ ? FUNCTIONAL_COLORS.skeletonLoadingDarkBackGround
+ : FUNCTIONAL_COLORS.skeletonLoadingLightBackGround,
+ skeletonLoadingHighlightMode: isDarkMode
+ ? FUNCTIONAL_COLORS.skeletonLoadingDarkHighlight
+ : FUNCTIONAL_COLORS.skeletonLoadingLightHighlight,
+ loading: isDarkMode
+ ? FUNCTIONAL_COLORS.pureWhite
+ : FUNCTIONAL_COLORS.primary,
};
}