This commit is contained in:
Zhiyuan Zheng 2021-02-17 21:39:38 +01:00
parent 13efb56324
commit 796b0544c4
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
12 changed files with 274 additions and 73 deletions

View File

@ -86,6 +86,7 @@ GEM
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-json (1.0.0)
fastlane-plugin-sentry (1.8.0)
fastlane-plugin-versioning_android (0.1.0)
fastlane-plugin-yarn (1.2)
gh_inspector (1.1.3)
@ -200,6 +201,7 @@ PLATFORMS
DEPENDENCIES
fastlane
fastlane-plugin-json
fastlane-plugin-sentry
fastlane-plugin-versioning_android
fastlane-plugin-yarn

View File

@ -2,7 +2,7 @@ fastlane_version "2.173.0"
skip_docs
ensure_env_vars(
env_vars: ["TOOOT_ENVIRONMENT"]
env_vars: ["TOOOT_ENVIRONMENT", "SENTRY_ORGANIZATION", "SENTRY_PROJECT", "SENTRY_AUTH_TOKEN"]
)
VERSIONS = read_json( json_path: "./package.json" )[:versions]
@ -15,7 +15,7 @@ case ENVIRONMENT
when "development"
GITHUB_RELEASE= ""
when "staging"
GITHUB_RELEASE = "v#{VERSION}-rc#{VERSIONS[:patch]}"
GITHUB_RELEASE = "v#{VERSION}-#{VERSIONS[:patch]}"
when "production"
GITHUB_RELEASE = "v#{VERSION}"
end
@ -37,6 +37,15 @@ private_lane :update_expo_ios do
set_info_plist_value( path: EXPO_PLIST, key: "EXUpdatesReleaseChannel", value: RELEASE_CHANNEL )
end
desc 'IOS: Upload dSYM'
lane :upload_symbols_ios do
download_dsyms
sentry_upload_dsym(
org_slug: ENV["SENTRY_ORGANIZATION"],
project_slug: ENV["SENTRY_PROJECT"]
)
end
desc "ANDROID: Prepare play store"
private_lane :prepare_playstore_android do
android_set_version_name( version_name: VERSION, gradle_file: "./android/app/build.gradle" )
@ -77,13 +86,14 @@ private_lane :build_ios do
when "staging"
prepare_appstore_ios
match( type: "appstore", readonly: true )
build_ios_app( export_method: "app-store" )
build_ios_app( export_method: "app-store", include_symbols: true, include_bitcode: true )
upload_to_testflight(
demo_account_required: true,
distribute_external: true,
groups: "内测用户",
changelog: "Ready for testing"
)
upload_symbols_ios
when "production"
prepare_appstore_ios
match( type: "appstore", readonly: true )

View File

@ -5,3 +5,4 @@
gem 'fastlane-plugin-yarn'
gem 'fastlane-plugin-json'
gem 'fastlane-plugin-versioning_android'
gem 'fastlane-plugin-sentry'

View File

@ -64,7 +64,7 @@
"react-native-tab-view-viewpager-adapter": "^1.1.0",
"react-native-toast-message": "^1.4.3",
"react-native-unimodules": "~0.12.0",
"react-query": "^3.8.2",
"react-query": "^3.9.7",
"react-redux": "^7.2.2",
"react-timeago": "^5.2.0",
"reconnecting-websocket": "^4.4.0",

View File

@ -162,12 +162,12 @@ const ComponentInstance: React.FC<Props> = ({
type='text'
content={t('server.button.local')}
onPress={processUpdate}
disabled={!instanceQuery.data?.uri || !agreed}
disabled={!instanceQuery.data?.uri}
loading={instanceQuery.isFetching || appsQuery.isFetching}
/>
</View>
<EULA agreed={agreed} setAgreed={setAgreed} />
{/* <EULA agreed={agreed} setAgreed={setAgreed} /> */}
<View>
<Placeholder

View File

@ -82,19 +82,26 @@ const Timeline: React.FC<Props> = ({
...queryKeyParams,
options: {
getPreviousPageParam: firstPage =>
firstPage.links?.prev && {
firstPage?.links?.prev && {
min_id: firstPage.links.prev,
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
limit: '6'
limit: '5'
},
getNextPageParam: lastPage =>
lastPage.links?.next && { max_id: lastPage.links.next }
lastPage?.links?.next && { max_id: lastPage.links.next }
}
})
const flattenData = data?.pages ? data.pages.flatMap(d => [...d.body]) : []
// Auto go back when toot page is empty
const navigation = useNavigation()
useEffect(() => {
if (toot && isSuccess && flattenData.length === 0) {
navigation.goBack()
}
}, [isSuccess, flattenData.length])
// Toot page auto scroll to selected toot
const flRef = useRef<FlatList<any>>(null)
const scrolled = useRef(false)
@ -119,13 +126,6 @@ const Timeline: React.FC<Props> = ({
350
)
}, [])
// Auto go back when toot page is empty
const navigation = useNavigation()
useEffect(() => {
if (toot && isSuccess && flattenData.length === 0) {
navigation.goBack()
}
}, [isSuccess, flattenData.length])
const keyExtractor = useCallback(({ id }) => id, [])
const renderItem = useCallback(
@ -233,26 +233,57 @@ const Timeline: React.FC<Props> = ({
scrollY.value = nativeEvent.contentOffset.y
}, [])
const onResponderRelease = useCallback(() => {
if (
scrollY.value <= -StyleConstants.Spacing.XL &&
isFetchingLatest === 0 &&
!disableRefresh
) {
haptics('Light')
setIsFetchingLatest(1)
flRef.current?.scrollToOffset({
animated: true,
offset: 1
})
if (!disableRefresh) {
const separation01 = -(
(StyleConstants.Spacing.M * 2.5) / 2 +
StyleConstants.Font.Size.S / 2
)
const separation02 = -(
StyleConstants.Spacing.M * 2.5 * 1.5 +
StyleConstants.Font.Size.S / 2
)
if (
scrollY.value <= separation02 &&
!isFetching &&
isFetchingLatest === 0
) {
haptics('Light')
queryClient.setQueryData<InfiniteData<any> | undefined>(
queryKey,
data => {
if (data?.pages[0].body.length === 0) {
return {
pages: data.pages.slice(1),
pageParams: data.pageParams.slice(1)
}
} else {
return data
}
}
)
refetch()
} else if (
scrollY.value <= separation01 &&
!isFetching &&
isFetchingLatest === 0
) {
haptics('Light')
setIsFetchingLatest(1)
flRef.current?.scrollToOffset({
animated: true,
offset: 1
})
}
}
}, [scrollY.value, isFetchingLatest, disableRefresh])
}, [scrollY.value, isFetching, isFetchingLatest, disableRefresh])
const headerPadding = useAnimatedStyle(() => {
if (isFetchingLatest !== 0) {
return { paddingTop: withTiming(StyleConstants.Spacing.XL) }
} else {
return { paddingTop: withTiming(0) }
return {
paddingTop:
isFetchingLatest !== 0 || (isFetching && !isLoading)
? withTiming(StyleConstants.Spacing.M * 2.5)
: withTiming(0)
}
}, [isFetchingLatest])
}, [isFetchingLatest, isFetching, isLoading])
const ListHeaderComponent = useMemo(
() => <Animated.View style={headerPadding} />,
[]
@ -267,7 +298,7 @@ const Timeline: React.FC<Props> = ({
colors={[theme.primary]}
progressBackgroundColor={theme.background}
refreshing={isFetching || isLoading}
// onRefresh={() => refetch()}
onRefresh={() => refetch()}
/>
)
},
@ -276,8 +307,14 @@ const Timeline: React.FC<Props> = ({
return (
<>
<TimelineRefresh isLoading={isLoading} disable={disableRefresh} />
<TimelineRefresh
scrollY={scrollY}
isLoading={isLoading}
isFetching={isFetching}
disable={disableRefresh}
/>
<FlatList
scrollEventThrottle={16}
onScroll={onScroll}
onResponderRelease={onResponderRelease}
ref={flRef}

View File

@ -13,6 +13,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { getLocalAccount } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { uniqBy } from 'lodash'
import React, { useCallback } from 'react'
import { Pressable, StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
@ -75,7 +76,6 @@ const TimelineDefault: React.FC<Props> = ({
}
]}
onPress={onPress}
disabled={queryKey && queryKey[1].toot !== undefined}
>
{item.reblog ? (
<TimelineActioned action='reblog' account={item.account} />
@ -143,11 +143,13 @@ const TimelineDefault: React.FC<Props> = ({
queryKey={queryKey}
rootQueryKey={rootQueryKey}
status={actualStatus}
accts={([actualStatus.account] as Mastodon.Account[] &
Mastodon.Mention[])
.concat(actualStatus.mentions)
.filter(d => d.id !== localAccount?.id)
.map(d => d.acct)}
accts={uniqBy(
([actualStatus.account] as Mastodon.Account[] &
Mastodon.Mention[])
.concat(actualStatus.mentions)
.filter(d => d.id !== localAccount?.id),
d => d.id
).map(d => d.acct)}
reblog={item.reblog ? true : false}
/>
</View>

View File

@ -13,6 +13,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { getLocalAccount } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { uniqBy } from 'lodash'
import React, { useCallback } from 'react'
import { Pressable, StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
@ -126,11 +127,13 @@ const TimelineNotifications: React.FC<Props> = ({
<TimelineActions
queryKey={queryKey}
status={notification.status}
accts={([notification.status.account] as Mastodon.Account[] &
Mastodon.Mention[])
.concat(notification.status.mentions)
.filter(d => d.id !== localAccount?.id)
.map(d => d.acct)}
accts={uniqBy(
([notification.status.account] as Mastodon.Account[] &
Mastodon.Mention[])
.concat(notification.status.mentions)
.filter(d => d.id !== localAccount?.id),
d => d.id
).map(d => d.acct)}
reblog={false}
/>
</View>

View File

@ -1,25 +1,164 @@
import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { StyleSheet, View } from 'react-native'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { Circle } from 'react-native-animated-spinkit'
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
export interface Props {
scrollY: Animated.SharedValue<number>
isLoading: boolean
isFetching: boolean
disable?: boolean
}
const TimelineRefresh: React.FC<Props> = ({ isLoading, disable = false }) => {
const { theme } = useTheme()
return !isLoading && !disable ? (
<View
style={styles.base}
children={
<Circle size={StyleConstants.Font.Size.L} color={theme.secondary} />
}
/>
) : null
}
const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5
const TimelineRefresh = React.memo(
({ scrollY, isLoading, isFetching, disable = false }: Props) => {
if (disable || isLoading) {
return null
}
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
const separation01 = -(
CONTAINER_HEIGHT / 2 +
StyleConstants.Font.Size.S / 2
)
const separation02 = -(
CONTAINER_HEIGHT * 1.5 +
StyleConstants.Font.Size.S / 2
)
const [textRight, setTextRight] = useState(0)
const arrowY = useAnimatedStyle(() => ({
transform: [
{
translateY: interpolate(
scrollY.value,
[0, separation01],
[
-CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.M / 2,
CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.S / 2
],
Extrapolate.CLAMP
)
}
]
}))
const arrowTop = useAnimatedStyle(() => ({
marginTop:
scrollY.value < separation02
? withTiming(CONTAINER_HEIGHT)
: withTiming(0)
}))
const arrowStage = useSharedValue(0)
const onLayout = useCallback(
({ nativeEvent }) => {
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
}
},
[textRight]
)
useAnimatedReaction(
() => {
if (isFetching) {
return false
}
switch (arrowStage.value) {
case 0:
if (scrollY.value < separation01) {
arrowStage.value = 1
return true
}
return false
case 1:
if (scrollY.value < separation02) {
arrowStage.value = 2
return true
}
if (scrollY.value > separation01) {
arrowStage.value = 0
return false
}
return false
case 2:
if (scrollY.value > separation02) {
arrowStage.value = 1
return false
}
return false
}
},
data => {
if (data) {
runOnJS(haptics)('Light')
}
},
[isFetching]
)
return (
<View style={styles.base}>
{isFetching ? (
<View style={styles.container2}>
<Circle size={StyleConstants.Font.Size.L} color={theme.secondary} />
</View>
) : (
<>
<View style={styles.container1}>
<Text
style={[styles.explanation, { color: theme.primary }]}
onLayout={onLayout}
children={t('refresh.fetchPreviousPage')}
/>
<Animated.View
style={[
{
position: 'absolute',
left: textRight + StyleConstants.Spacing.S
},
arrowY,
arrowTop
]}
children={
<Icon
name='ArrowLeft'
size={StyleConstants.Font.Size.M}
color={theme.primary}
/>
}
/>
</View>
<View style={styles.container2}>
<Text
style={[styles.explanation, { color: theme.primary }]}
onLayout={onLayout}
children={t('refresh.refetch')}
/>
</View>
</>
)}
</View>
)
},
(prev, next) =>
prev.isLoading === next.isLoading && prev.isFetching === next.isFetching
)
const styles = StyleSheet.create({
base: {
@ -27,9 +166,18 @@ const styles = StyleSheet.create({
top: 0,
left: 0,
right: 0,
height: StyleConstants.Spacing.XL,
justifyContent: 'center',
height: CONTAINER_HEIGHT * 2,
alignItems: 'center'
},
container1: {
flex: 1,
flexDirection: 'row',
height: CONTAINER_HEIGHT
},
container2: { height: CONTAINER_HEIGHT, justifyContent: 'center' },
explanation: {
fontSize: StyleConstants.Font.Size.S,
lineHeight: CONTAINER_HEIGHT
}
})

View File

@ -11,10 +11,9 @@ export default {
end: {
message: 'The end, what about a cup of <0 />'
},
header: {
explanation:
'External instance might not be known to logged in instance, thus actions are not allowed but only for reading. You can switch to any instance in the settings.',
button: 'Go to settings'
refresh: {
fetchPreviousPage: 'Refresh upwards',
refetch: 'Refresh all'
},
shared: {
actioned: {

View File

@ -11,10 +11,9 @@ export default {
end: {
message: '居然刷到底了,喝杯 <0 /> 吧'
},
header: {
explanation:
'围观的社区可能不属于已经登录的社区的已知连结,因此只可围观嘟文,不能进行操作。设置里可以切换想要围观的社区。',
button: '前往设置'
refresh: {
fetchPreviousPage: '向上刷新',
refetch: '完全刷新'
},
shared: {
actioned: {

View File

@ -8736,10 +8736,10 @@ react-navigation@*, react-navigation@^4.4.3:
"@react-navigation/core" "^3.7.9"
"@react-navigation/native" "^3.8.3"
react-query@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.8.2.tgz#e2dac76b5d9b3465d854f4ca040a35ba4bcae1ae"
integrity sha512-Ha8+WZLIHOUkKhqE4+2RZZB03WvEWmNhN4WIIw3zWVCtR7blo2n2TXtu+d3YhaY1o6dt+sHT+J+zNV8IzR583w==
react-query@^3.9.7:
version "3.9.7"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.9.7.tgz#324c697f418827c129c8c126d233c6052bb1e35e"
integrity sha512-vpQgRFOljd7Lr1wL8hOwxWzb7awLIjaeqaq6DJ1fzA8N9mK1fAkK+UVrt8WaXJGBfz7JEnfCiXuENQspk0N7Sw==
dependencies:
"@babel/runtime" "^7.5.5"
match-sorter "^6.0.2"