This commit is contained in:
Zhiyuan Zheng 2021-02-01 02:16:53 +01:00
parent 1072d88191
commit b8aa402c99
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
20 changed files with 179 additions and 252 deletions

60
.gitignore vendored
View File

@ -1,15 +1,3 @@
node_modules/
.expo/
.expo-shared/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS # macOS
.DS_Store .DS_Store
@ -17,46 +5,13 @@ web-build/
coverage/ coverage/
builds/ builds/
fastlane/id_rsa
# @generated expo-cli sync-28e2ab0e9ece60556eaf932abe52d017ec33db50 # @generated expo-cli sync-28e2ab0e9ece60556eaf932abe52d017ec33db50
# The following patterns were generated by expo-cli # The following patterns were generated by expo-cli
# OSX # OSX
#
.DS_Store .DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
# node.js # node.js
#
node_modules/ node_modules/
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
@ -67,25 +22,12 @@ buck-out/
*.keystore *.keystore
!debug.keystore !debug.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifacts # Bundle artifacts
*.jsbundle *.jsbundle
# CocoaPods
/ios/Pods/
# Expo # Expo
.expo/* .expo/*
.expo-shared/*
web-build/ web-build/
# @end expo-cli # @end expo-cli

6
android/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
build/
.idea
.gradle
local.properties
*.iml
*.hprof

View File

@ -7,7 +7,7 @@ export default (): ExpoConfig => ({
slug: 'tooot', slug: 'tooot',
privacy: 'hidden', privacy: 'hidden',
sdkVersion: '40.0.0', sdkVersion: '40.0.0',
version: '0.1.0', version: '0.8',
platforms: ['ios', 'android'], platforms: ['ios', 'android'],
orientation: 'portrait', orientation: 'portrait',
userInterfaceStyle: 'automatic', userInterfaceStyle: 'automatic',

8
fastlane/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.key
*.jks
*.p8
*.p12
/report.xml
Preview.html
screenshots

1
fastlane/Appfile Normal file
View File

@ -0,0 +1 @@
app_identifier "com.xmflsct.app.tooot"

View File

@ -1,28 +1,12 @@
$ExpoSDK = '40.0.0' $ExpoSDK = '40.0.0'
$ExpoRelease = '40' $NativeVersion = '210201' # Update when there is native module change
fastlane_version '2.172.0' fastlane_version '2.172.0'
platform :ios do platform :ios do
desc 'Build and deploy' desc 'Build and deploy'
private_lane :build do |options| private_lane :build do |options|
branch = 'RELEASE-TYPE'.gsub('RELEASE', $ExpoRelease).gsub('TYPE', options[:type]) branch = 'NATIVEVERSION-TYPE'.gsub('NATIVEVERSION', $NativeVersion).gsub('TYPE', options[:type])
case options[:type]
when 'staging', 'production'
ensure_git_branch(
branch: branch
)
ensure_git_status_clean
build_number = Time.new.strftime('%y%m%d%k')
increment_build_number build_number: build_number
end
match(
type: options[:type],
readonly: true
)
set_info_plist_value( set_info_plist_value(
path: './ios/tooot/Supporting/Expo.plist', path: './ios/tooot/Supporting/Expo.plist',
key: 'EXUpdatesSDKVersion', key: 'EXUpdatesSDKVersion',
@ -34,6 +18,25 @@ platform :ios do
value: branch value: branch
) )
case options[:type]
when 'staging', 'production'
ensure_git_branch(
branch: options[:type]
)
ensure_git_status_clean
increment_build_number(
build_number: $NativeVersion
)
app_store_connect_api_key(
key_filepath: "appstore.p8"
)
end
match(
type: options[:type],
readonly: true
)
case options[:type] case options[:type]
when 'development' when 'development'
build_ios_app( build_ios_app(
@ -41,13 +44,10 @@ platform :ios do
silent: true, silent: true,
include_bitcode: true, include_bitcode: true,
workspace: './ios/tooot.xcworkspace', workspace: './ios/tooot.xcworkspace',
output_directory: './build/ios',
output_name: 'development',
export_method: 'development' export_method: 'development'
) )
install_on_device( install_on_device(
skip_wifi: true, skip_wifi: true
ipa: './build/ios/development.ipa'
) )
when 'staging' when 'staging'
build_ios_app( build_ios_app(
@ -55,8 +55,8 @@ platform :ios do
workspace: './ios/tooot.xcworkspace' workspace: './ios/tooot.xcworkspace'
) )
upload_to_testflight( upload_to_testflight(
api_key: '{"key_id": "KEY_ID", "issuer_id": "ISSUER_ID", "key_filepath": "appstore.p8"}'.gsub('KEY_ID', ENV['APP_STORE_KEY_ID']).gsub('ISSUER_ID', ENV['APP_STORE_ISSUER_ID']), skip_submission: true,
skip_submission: true notify_external_testers: false
) )
end end
end end

View File

@ -1,10 +1,3 @@
git_url(ENV('MATCH_GIT_REPO'))
git_user_email("me@xmflsct.com") git_user_email("me@xmflsct.com")
git_private_key("./id_rsa") git_private_key("./github.key")
storage_mode("git") storage_mode("git")
type("development")
app_identifier(["com.xmflsct.app.tooot"])
username(ENV['APP_STORE_EMAIL'])

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="fastlane.lanes">
<testcase classname="fastlane.lanes" name="0: Verifying fastlane version" time="0.000454">
</testcase>
<testcase classname="fastlane.lanes" name="1: Switch to ios build lane" time="0.00019">
</testcase>
<testcase classname="fastlane.lanes" name="2: match" time="3.907079">
</testcase>
<testcase classname="fastlane.lanes" name="3: set_info_plist_value" time="0.003664">
</testcase>
<testcase classname="fastlane.lanes" name="4: set_info_plist_value" time="0.002893">
</testcase>
<testcase classname="fastlane.lanes" name="5: build_ios_app" time="557.460706">
</testcase>
<testcase classname="fastlane.lanes" name="6: install_on_device" time="3.305225">
</testcase>
</testsuite>
</testsuites>

22
ios/.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
/Pods/
/build/
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace

View File

@ -9,7 +9,7 @@
<key>EXUpdatesLaunchWaitMs</key> <key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer> <integer>0</integer>
<key>EXUpdatesReleaseChannel</key> <key>EXUpdatesReleaseChannel</key>
<string>40-development</string> <string>210201-development</string>
<key>EXUpdatesSDKVersion</key> <key>EXUpdatesSDKVersion</key>
<string>40.0.0</string> <string>40.0.0</string>
<key>EXUpdatesURL</key> <key>EXUpdatesURL</key>

View File

@ -13,61 +13,59 @@ export interface Props {
fontBold?: boolean fontBold?: boolean
} }
const ParseEmojis: React.FC<Props> = ({ const ParseEmojis = React.memo(
content, ({ content, emojis, size = 'M', fontBold = false }: Props) => {
emojis, const { mode, theme } = useTheme()
size = 'M', const styles = useMemo(() => {
fontBold = false return StyleSheet.create({
}) => { text: {
const { mode, theme } = useTheme() color: theme.primary,
const styles = useMemo(() => { ...StyleConstants.FontStyle[size],
return StyleSheet.create({ ...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
text: { },
color: theme.primary, image: {
...StyleConstants.FontStyle[size], width: StyleConstants.Font.Size[size],
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold }) height: StyleConstants.Font.Size[size],
}, transform: [{ translateY: size === 'L' ? -3 : -1 }]
image: { }
width: StyleConstants.Font.Size[size], })
height: StyleConstants.Font.Size[size], }, [mode])
transform: [{ translateY: size === 'L' ? -3 : -1 }]
}
})
}, [mode])
return ( return (
<Text style={styles.text}> <Text style={styles.text}>
{emojis ? ( {emojis ? (
content content
.split(regexEmoji) .split(regexEmoji)
.filter(f => f) .filter(f => f)
.map((str, i) => { .map((str, i) => {
if (str.match(regexEmoji)) { if (str.match(regexEmoji)) {
const emojiShortcode = str.split(regexEmoji)[1] const emojiShortcode = str.split(regexEmoji)[1]
const emojiIndex = emojis.findIndex(emoji => { const emojiIndex = emojis.findIndex(emoji => {
return emojiShortcode === `:${emoji.shortcode}:` return emojiShortcode === `:${emoji.shortcode}:`
}) })
return emojiIndex === -1 ? ( return emojiIndex === -1 ? (
<Text key={i}>{emojiShortcode}</Text> <Text key={i}>{emojiShortcode}</Text>
) : ( ) : (
<Text key={i}> <Text key={i}>
{/* When emoji starts a paragraph, lineHeight will break */} {/* When emoji starts a paragraph, lineHeight will break */}
{i === 0 ? <Text> </Text> : null} {i === 0 ? <Text> </Text> : null}
<FastImage <FastImage
source={{ uri: emojis[emojiIndex].url }} source={{ uri: emojis[emojiIndex].url }}
style={styles.image} style={styles.image}
/> />
</Text> </Text>
) )
} else { } else {
return <Text key={i}>{str}</Text> return <Text key={i}>{str}</Text>
} }
}) })
) : ( ) : (
<Text>{content}</Text> <Text>{content}</Text>
)} )}
</Text> </Text>
) )
} },
(prev, next) => prev.content === next.content
)
export default React.memo(ParseEmojis, () => true) export default ParseEmojis

View File

@ -30,8 +30,8 @@ const renderNode = ({
theme: any theme: any
node: any node: any
index: number index: number
size: 'M' | 'L' size: 'S' | 'M' | 'L'
navigation: StackNavigationProp<Nav.LocalStackParamList> navigation: StackNavigationProp<Nav.TabLocalStackParamList>
mentions?: Mastodon.Mention[] mentions?: Mastodon.Mention[]
tags?: Mastodon.Tag[] tags?: Mastodon.Tag[]
showFullLink: boolean showFullLink: boolean
@ -145,7 +145,7 @@ const renderNode = ({
export interface Props { export interface Props {
content: string content: string
size?: 'M' | 'L' size?: 'S' | 'M' | 'L'
emojis?: Mastodon.Emoji[] emojis?: Mastodon.Emoji[]
mentions?: Mastodon.Mention[] mentions?: Mastodon.Mention[]
tags?: Mastodon.Tag[] tags?: Mastodon.Tag[]
@ -167,7 +167,7 @@ const ParseHTML: React.FC<Props> = ({
disableDetails = false disableDetails = false
}) => { }) => {
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.LocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const route = useRoute() const route = useRoute()
const { theme } = useTheme() const { theme } = useTheme()

View File

@ -82,11 +82,10 @@ const Timeline: React.FC<Props> = ({
const navigation = useNavigation() const navigation = useNavigation()
useEffect(() => { useEffect(() => {
const unsubscribe = navigation.addListener('focus', props => { const unsubscribe = navigation.addListener('focus', props => {
if (props.target && props.target.includes('Screen-Notifications-Root')) { if (props.target && props.target.includes('Tab-Notifications-Root')) {
if (flattenData.length) { if (flattenData.length) {
dispatch( dispatch(
localUpdateNotification({ localUpdateNotification({
unread: false,
latestTime: (flattenData[0] as Mastodon.Notification).created_at latestTime: (flattenData[0] as Mastodon.Notification).created_at
}) })
) )

View File

@ -47,6 +47,6 @@ export default {
heading: 'Help us improve', heading: 'Help us improve',
description: 'Collecting only non-user relative usage' description: 'Collecting only non-user relative usage'
}, },
version: 'Version v{{version}}' version: 'Version v{{version}} ({{releaseChannel}})'
} }
} }

View File

@ -47,6 +47,6 @@ export default {
heading: '帮助我们改进', heading: '帮助我们改进',
description: '收集不与用户相关联的使用信息' description: '收集不与用户相关联的使用信息'
}, },
version: '版本 v{{version}}' version: '版本 v{{version}} ({{releaseChannel}})'
} }
} }

View File

@ -90,7 +90,7 @@ const ScreenTabs: React.FC<ScreenTabsProp> = ({ navigation }) => {
}), }),
[localActiveIndex, localAccount] [localActiveIndex, localAccount]
) )
const tabNavigatorTabBarOptions = useMemo( const tabBarOptions = useMemo(
() => ({ () => ({
activeTintColor: theme.primary, activeTintColor: theme.primary,
inactiveTintColor: inactiveTintColor:
@ -100,7 +100,7 @@ const ScreenTabs: React.FC<ScreenTabsProp> = ({ navigation }) => {
}), }),
[theme, localActiveIndex] [theme, localActiveIndex]
) )
const tabScreenLocalListeners = useCallback( const localListeners = useCallback(
() => ({ () => ({
tabPress: (e: any) => { tabPress: (e: any) => {
if (!(localActiveIndex !== null)) { if (!(localActiveIndex !== null)) {
@ -110,7 +110,7 @@ const ScreenTabs: React.FC<ScreenTabsProp> = ({ navigation }) => {
}), }),
[localActiveIndex] [localActiveIndex]
) )
const tabScreenComposeListeners = useMemo( const composeListeners = useMemo(
() => ({ () => ({
tabPress: (e: any) => { tabPress: (e: any) => {
e.preventDefault() e.preventDefault()
@ -122,8 +122,8 @@ const ScreenTabs: React.FC<ScreenTabsProp> = ({ navigation }) => {
}), }),
[localActiveIndex] [localActiveIndex]
) )
const tabScreenComposeComponent = useCallback(() => null, []) const composeComponent = useCallback(() => null, [])
const tabScreenNotificationsListeners = useCallback( const notificationsListeners = useCallback(
() => ({ () => ({
tabPress: (e: any) => { tabPress: (e: any) => {
if (!(localActiveIndex !== null)) { if (!(localActiveIndex !== null)) {
@ -144,66 +144,67 @@ const ScreenTabs: React.FC<ScreenTabsProp> = ({ navigation }) => {
} }
}) })
const prevNotification = useSelector(getLocalNotification) const prevNotification = useSelector(getLocalNotification)
useEffect(() => { const notificationsOptions = useMemo(() => {
if (queryNotification.data?.pages) { const badge = {
const flattenData = queryNotification.data.pages.flatMap(d => [...d]) show: {
const latestNotificationTime = flattenData.length tabBarBadge: '',
? (flattenData[0] as Mastodon.Notification).created_at tabBarBadgeStyle: {
: undefined transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
},
hide: {
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
}
const flattenData = queryNotification.data?.pages.flatMap(d => [...d])
const latestNotificationTime = flattenData?.length
? (flattenData[0] as Mastodon.Notification).created_at
: undefined
if (!prevNotification || !prevNotification.latestTime) { if (prevNotification?.latestTime) {
dispatch(localUpdateNotification({ unread: false })) if (
} else if (
latestNotificationTime && latestNotificationTime &&
new Date(prevNotification.latestTime) < new Date(latestNotificationTime) new Date(prevNotification.latestTime) < new Date(latestNotificationTime)
) { ) {
dispatch( return badge.show
localUpdateNotification({ } else {
unread: true, return badge.hide
latestTime: latestNotificationTime }
}) } else {
) if (latestNotificationTime) {
return badge.show
} else {
return badge.hide
} }
} }
}, [queryNotification.data?.pages]) }, [prevNotification, queryNotification.data?.pages])
return ( return (
<Tab.Navigator <Tab.Navigator
initialRouteName={localActiveIndex !== null ? 'Tab-Local' : 'Tab-Me'} initialRouteName={localActiveIndex !== null ? 'Tab-Local' : 'Tab-Me'}
screenOptions={screenOptions} screenOptions={screenOptions}
tabBarOptions={tabNavigatorTabBarOptions} tabBarOptions={tabBarOptions}
> >
<Tab.Screen <Tab.Screen
name='Tab-Local' name='Tab-Local'
component={TabLocal} component={TabLocal}
listeners={tabScreenLocalListeners} listeners={localListeners}
/> />
<Tab.Screen name='Tab-Public' component={TabPublic} /> <Tab.Screen name='Tab-Public' component={TabPublic} />
<Tab.Screen <Tab.Screen
name='Tab-Compose' name='Tab-Compose'
component={tabScreenComposeComponent} component={composeComponent}
listeners={tabScreenComposeListeners} listeners={composeListeners}
/> />
<Tab.Screen <Tab.Screen
name='Tab-Notifications' name='Tab-Notifications'
component={TabNotifications} component={TabNotifications}
listeners={tabScreenNotificationsListeners} listeners={notificationsListeners}
options={ options={notificationsOptions}
prevNotification && prevNotification.unread
? {
tabBarBadge: '',
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
: {
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
}
/> />
<Tab.Screen name='Tab-Me' component={TabMe} /> <Tab.Screen name='Tab-Me' component={TabMe} />
</Tab.Navigator> </Tab.Navigator>

View File

@ -29,7 +29,10 @@ const SettingsAnalytics: React.FC = () => {
} }
/> />
<Text style={[styles.version, { color: theme.secondary }]}> <Text style={[styles.version, { color: theme.secondary }]}>
{t('content.version', { version: Constants.manifest.version })} {t('content.version', {
version: Constants.manifest.version,
releaseChannel: Constants.manifest.releaseChannel || 'dev'
})}
</Text> </Text>
</MenuContainer> </MenuContainer>
) )

View File

@ -75,7 +75,7 @@ const AccountInformation: React.FC<Props> = ({ account, myInfo = false }) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
base: { base: {
marginTop: -StyleConstants.Spacing.Global.PagePadding * 3, marginTop: -StyleConstants.Avatar.L / 2,
padding: StyleConstants.Spacing.Global.PagePadding padding: StyleConstants.Spacing.Global.PagePadding
}, },
avatarAndActions: { avatarAndActions: {

View File

@ -25,7 +25,7 @@ const AccountInformationFields = React.memo(
> >
<ParseHTML <ParseHTML
content={field.name} content={field.name}
size={'M'} size={'S'}
emojis={account.emojis} emojis={account.emojis}
showFullLink showFullLink
numberOfLines={5} numberOfLines={5}
@ -42,7 +42,7 @@ const AccountInformationFields = React.memo(
<View style={styles.fieldRight}> <View style={styles.fieldRight}>
<ParseHTML <ParseHTML
content={field.value} content={field.value}
size={'M'} size={'S'}
emojis={account.emojis} emojis={account.emojis}
showFullLink showFullLink
numberOfLines={5} numberOfLines={5}

View File

@ -20,7 +20,6 @@ export type InstanceLocal = {
preferences: Mastodon.Preferences preferences: Mastodon.Preferences
} }
notification: { notification: {
unread: boolean
latestTime?: Mastodon.Notification['created_at'] latestTime?: Mastodon.Notification['created_at']
} }
} }
@ -115,7 +114,7 @@ export const localAddInstance = createAsyncThunk(
preferences preferences
}, },
notification: { notification: {
unread: false latestTime: undefined
} }
} }
}) })
@ -203,10 +202,8 @@ const instancesSlice = createSlice({
state, state,
action: PayloadAction<Partial<InstanceLocal['notification']>> action: PayloadAction<Partial<InstanceLocal['notification']>>
) => { ) => {
state.local.instances[state.local.activeIndex!].notification = { state.local.instances[state.local.activeIndex!].notification =
...state.local.instances[state.local.activeIndex!].notification, action.payload
...action.payload
}
}, },
remoteUpdate: ( remoteUpdate: (
state, state,