Added fastlane and other updates

This commit is contained in:
Zhiyuan Zheng 2021-01-31 03:09:35 +01:00
parent 253ddee319
commit 1072d88191
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
43 changed files with 755 additions and 185 deletions

5
.envrc
View File

@ -1,5 +0,0 @@
export SENTRY_ORGANIZATION="xmflsct"
export SENTRY_PROJECT="tooot-app"
export SENTRY_DEPLOY_ENV="expo"
export SENTRY_AUTH_TOKEN="dbccffb69144454784f2171ee7e39211b54392e5b535439aa5b77f2681578f4c"
export SENTRY_DSN="https://835b42fb2b25463284edb5a7e18c377d@o389581.ingest.sentry.io/5571975"

View File

@ -1,8 +1,8 @@
name: Publish testing
name: Publish development
on:
push:
branches:
- '*-testing'
- '*-development'
jobs:
publish:
runs-on: ubuntu-latest
@ -27,5 +27,5 @@ jobs:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_DEPLOY_ENV: testing
SENTRY_DEPLOY_ENV: development
run: expo publish --release-channel=${GITHUB_REF#refs/heads/}

2
.gitignore vendored
View File

@ -17,6 +17,8 @@ web-build/
coverage/
builds/
fastlane/id_rsa
# @generated expo-cli sync-28e2ab0e9ece60556eaf932abe52d017ec33db50
# The following patterns were generated by expo-cli

3
Gemfile Normal file
View File

@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

201
Gemfile.lock Normal file
View File

@ -0,0 +1,201 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.3)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.422.0)
aws-sdk-core (3.111.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.41.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.87.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.3)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.1)
excon (0.78.1)
faraday (1.3.0)
faraday-net_http (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-net_http (1.0.1)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.1)
fastlane (2.172.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-api-client (0.38.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-apis-core (0.2.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.14)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
rexml
signet (~> 0.14)
webrick
google-apis-iamcredentials_v1 (0.1.0)
google-apis-core (~> 0.1)
google-apis-storage_v1 (0.1.0)
google-apis-core (~> 0.1)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
google-cloud-storage (1.30.0)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.15.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.5.1)
jwt (2.2.2)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.0.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
os (1.1.1)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.3)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.4)
rouge (2.0.7)
ruby2_keywords (0.0.4)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.1)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
BUNDLED WITH
1.17.2

View File

@ -43,4 +43,5 @@
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
</application>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application android:requestLegacyExternalStorage="true"/>
</manifest>

82
fastlane/Fastfile Normal file
View File

@ -0,0 +1,82 @@
$ExpoSDK = '40.0.0'
$ExpoRelease = '40'
fastlane_version '2.172.0'
platform :ios do
desc 'Build and deploy'
private_lane :build do |options|
branch = 'RELEASE-TYPE'.gsub('RELEASE', $ExpoRelease).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(
path: './ios/tooot/Supporting/Expo.plist',
key: 'EXUpdatesSDKVersion',
value: $ExpoSDK
)
set_info_plist_value(
path: './ios/tooot/Supporting/Expo.plist',
key: 'EXUpdatesReleaseChannel',
value: branch
)
case options[:type]
when 'development'
build_ios_app(
scheme: 'tooot',
silent: true,
include_bitcode: true,
workspace: './ios/tooot.xcworkspace',
output_directory: './build/ios',
output_name: 'development',
export_method: 'development'
)
install_on_device(
skip_wifi: true,
ipa: './build/ios/development.ipa'
)
when 'staging'
build_ios_app(
scheme: 'tooot',
workspace: './ios/tooot.xcworkspace'
)
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
)
end
end
desc 'Build development to phone'
lane :development do
build(type: 'development')
end
desc 'Build staging to TestFlight'
lane :staging do
build(type: 'staging')
end
desc 'Build product to App Store'
lane :production do
build(type: 'production')
end
end
platform :android do
# Android Lanes
end

10
fastlane/Matchfile Normal file
View File

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

39
fastlane/README.md Normal file
View File

@ -0,0 +1,39 @@
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew install fastlane`
# Available Actions
## iOS
### ios development
```
fastlane ios development
```
Build development to phone
### ios staging
```
fastlane ios staging
```
Build staging to TestFlight
### ios production
```
fastlane ios production
```
Build product to App Store
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

43
fastlane/report.xml Normal file
View File

@ -0,0 +1,43 @@
<?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>

View File

@ -22,3 +22,14 @@ target 'tooot' do
# flipper_post_install(installer)
# end
end
# https://github.com/CocoaPods/CocoaPods/issues/9884
post_install do |pi|
pi.pods_project.targets.each do |t|
t.build_configurations.each do |bc|
if bc.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] == '8.0'
bc.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
end
end
end
end

View File

@ -334,6 +334,8 @@ PODS:
- React
- react-native-blurhash (1.0.29):
- React
- react-native-cameraroll (4.0.2):
- React-Core
- react-native-netinfo (5.9.10):
- React-Core
- react-native-safe-area-context (3.1.9):
@ -524,6 +526,7 @@ DEPENDENCIES:
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- react-native-blurhash (from `../node_modules/react-native-blurhash`)
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-segmented-control (from `../node_modules/@react-native-community/segmented-control`)"
@ -669,6 +672,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/blur"
react-native-blurhash:
:path: "../node_modules/react-native-blurhash"
react-native-cameraroll:
:path: "../node_modules/@react-native-community/cameraroll"
react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo"
react-native-safe-area-context:
@ -802,6 +807,7 @@ SPEC CHECKSUMS:
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-blurhash: 90886ae897cafbbdf2773cb3654656bcb34e8f43
react-native-cameraroll: 1965db75c851b15e77a22ca0ac78e32af6b571ae
react-native-netinfo: 30fb89fa913c342be82a887b56e96be6d71201dd
react-native-safe-area-context: b6e0e284002381d2ff29fa4fff42b4d8282e3c94
react-native-segmented-control: 65df6cd0619b780b3843d574a72d4c7cec396097
@ -843,6 +849,6 @@ SPEC CHECKSUMS:
UMTaskManagerInterface: 4c60b43eaf3cb05a164bc9113258a171c18b7bf7
Yoga: 4bd86afe9883422a7c4028c00e34790f560923d6
PODFILE CHECKSUM: cda6b7a1593395b311286b33b0036167ce6f0a15
PODFILE CHECKSUM: 45a588d6415c6afb7aa7c5ef73a2ca1e38011f49
COCOAPODS: 1.10.1

View File

@ -19,7 +19,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>4</string>
<string>0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
@ -74,6 +74,8 @@
<string>Allow $(PRODUCT_NAME) to access your microphone</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Give $(PRODUCT_NAME) permission to save photos</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Give $(PRODUCT_NAME) permission to save photos</string>
<key>NSCameraUsageDescription</key>
<string>Give $(PRODUCT_NAME) permission to access your camera</string>
<key>NSMicrophoneUsageDescription</key>

View File

@ -1,16 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EXUpdatesSDKVersion</key>
<string>40.0.0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@xmflsct/tooot</string>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesCheckOnLaunch</key>
<string>ALWAYS</string>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
</dict>
</plist>
<dict>
<key>EXUpdatesCheckOnLaunch</key>
<string>WIFI_ONLY</string>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesReleaseChannel</key>
<string>40-development</string>
<key>EXUpdatesSDKVersion</key>
<string>40.0.0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@xmflsct/tooot</string>
</dict>
</plist>

View File

@ -3,8 +3,7 @@
"start": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios",
"web": "expo start --web",
"eject": "expo eject",
"ios:development": "bundle exec fastlane ios development",
"test": "jest --watchAll",
"release": "scripts/release.sh"
},
@ -13,6 +12,7 @@
"@neverdull-agency/expo-unlimited-secure-store": "^1.0.10",
"@react-native-async-storage/async-storage": "^1.13.3",
"@react-native-community/blur": "^3.6.0",
"@react-native-community/cameraroll": "^4.0.2",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "^5.9.10",
"@react-native-community/segmented-control": "2.2.2",

View File

@ -9,12 +9,16 @@ interface IImageInfo {
declare namespace Nav {
type RootStackParamList = {
'Screen-Tabs': undefined
'Screen-Actions': {
queryKey: QueryKeyTimeline
status: Mastodon.Status
url?: string
type?: 'status' | 'account'
}
'Screen-Actions':
| {
type: 'status'
queryKey: QueryKeyTimeline
status: Mastodon.Status
}
| {
type: 'account'
account: Mastodon.Account
}
'Screen-Announcements': { showAll: boolean }
'Screen-Compose':
| {
@ -61,6 +65,11 @@ declare namespace Nav {
}
}
type ScreenComposeStackParamList = {
'Screen-Compose-Root': undefined
'Screen-Compose-EditAttachment': { index: number }
}
type ScreenTabsStackParamList = {
'Tab-Local': undefined
'Tab-Public': undefined
@ -95,11 +104,6 @@ declare namespace Nav {
'Tab-Public-Root': undefined
} & TabSharedStackParamList
type TabComposeStackParamList = {
'Tab-Compose-Root': undefined
'Tab-Compose-EditAttachment': unknown
}
type TabNotificationsStackParamList = {
'Tab-Notifications-Root': undefined
} & TabSharedStackParamList

View File

@ -200,7 +200,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
}}
sharedElements={route => {
const { imageIndex, imageUrls } = route.params
return [{ id: `image.${imageUrls[imageIndex].url}`, debug: true }]
return [{ id: `image.${imageUrls[imageIndex].url}` }]
}}
/>
</Stack.Navigator>

View File

@ -28,5 +28,7 @@ export default {
componentParse: require('./components/parse').default,
componentRelationship: require('./components/relationship').default,
componentRelativeTime: require('./components/relativeTime').default,
componentTimeline: require('./components/timeline').default
componentTimeline: require('./components/timeline').default,
screenImageViewer: require('./screens/screenImageViewer').default
}

View File

@ -9,7 +9,8 @@ export default {
heading: '$t(sharedAnnouncements:heading)',
content: {
unread: '{{amount}} unread',
read: 'All read'
read: 'All read',
empty: 'None'
}
}
},

View File

@ -40,6 +40,9 @@ export default {
review: {
heading: 'Review tooot'
},
contact: {
heading: 'Contact tooot'
},
analytics: {
heading: 'Help us improve',
description: 'Collecting only non-user relative usage'

View File

@ -0,0 +1,13 @@
export default {
content: {
options: {
save: 'Save image',
share: 'Share iamge',
cancel: '$t(common:buttons.cancel)'
},
save: {
function: 'Saving image',
success: 'Image saved'
}
}
}

View File

@ -28,5 +28,7 @@ export default {
componentParse: require('./components/parse').default,
componentRelationship: require('./components/relationship').default,
componentRelativeTime: require('./components/relativeTime').default,
componentTimeline: require('./components/timeline').default
componentTimeline: require('./components/timeline').default,
screenImageViewer: require('./screens/screenImageViewer').default
}

View File

@ -9,7 +9,8 @@ export default {
heading: '$t(sharedAnnouncements:heading)',
content: {
unread: '{{amount}} 条未读公告',
read: '无未读公告'
read: '无未读公告',
empty: '无公告'
}
}
},

View File

@ -40,6 +40,9 @@ export default {
review: {
heading: '给 tooot 打分'
},
contact: {
heading: '联系 tooot'
},
analytics: {
heading: '帮助我们改进',
description: '收集不与用户相关联的使用信息'

View File

@ -0,0 +1,13 @@
export default {
content: {
options: {
save: '保存图片',
share: '分享图片',
cancel: '$t(common:buttons.cancel)'
},
save: {
function: '保存图片',
success: '图片保存成功'
}
}
}

View File

@ -2,7 +2,7 @@ import { StackScreenProps } from '@react-navigation/stack'
import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect } from 'react'
import React, { useCallback, useEffect, useMemo } from 'react'
import { Dimensions, StyleSheet, View } from 'react-native'
import {
PanGestureHandler,
@ -31,20 +31,29 @@ export type ScreenAccountProp = StackScreenProps<
>
const ScreenActions = React.memo(
({
route: {
params: { queryKey, status, url, type }
},
navigation
}: ScreenAccountProp) => {
({ route: { params }, navigation }: ScreenAccountProp) => {
const localAccount = useSelector(getLocalAccount)
const sameAccount = localAccount?.id === status.account.id
let sameAccount = false
switch (params.type) {
case 'status':
sameAccount = localAccount?.id === params.status.account.id
break
case 'account':
sameAccount = localAccount?.id === params.account.id
break
}
const localDomain = useSelector(getLocalUrl)
const statusDomain = status.uri
? status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
: ''
const sameDomain = localDomain === statusDomain
let sameDomain = true
let statusDomain: string
switch (params.type) {
case 'status':
statusDomain = params.status.uri
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
: ''
sameDomain = localDomain === statusDomain
break
}
const { theme } = useTheme()
const insets = useSafeAreaInsets()
@ -82,6 +91,56 @@ const ScreenActions = React.memo(
}
})
const actions = useMemo(() => {
switch (params.type) {
case 'status':
return (
<>
{!sameAccount && (
<ActionsAccount
queryKey={params.queryKey}
account={params.status.account}
dismiss={dismiss}
/>
)}
{sameAccount && params.status && (
<ActionsStatus
navigation={navigation}
queryKey={params.queryKey}
status={params.status}
dismiss={dismiss}
/>
)}
{!sameDomain && statusDomain && (
<ActionsDomain
queryKey={params.queryKey}
domain={statusDomain}
dismiss={dismiss}
/>
)}
<ActionsShare
url={params.status.url || params.status.uri}
type={params.type}
dismiss={dismiss}
/>
</>
)
case 'account':
return (
<>
{!sameAccount && (
<ActionsAccount account={params.account} dismiss={dismiss} />
)}
<ActionsShare
url={params.account.url}
type={params.type}
dismiss={dismiss}
/>
</>
)
}
}, [])
return (
<Animated.View style={{ flex: 1 }}>
<TapGestureHandler
@ -114,34 +173,7 @@ const ScreenActions = React.memo(
{ backgroundColor: theme.primaryOverlay }
]}
/>
{!sameAccount && (
<ActionsAccount
queryKey={queryKey}
account={status.account}
dismiss={dismiss}
/>
)}
{sameAccount && status && (
<ActionsStatus
navigation={navigation}
queryKey={queryKey}
status={status}
dismiss={dismiss}
/>
)}
{!sameDomain && (
<ActionsDomain
queryKey={queryKey}
domain={statusDomain}
dismiss={dismiss}
/>
)}
{url && type ? (
<ActionsShare url={url} type={type} dismiss={dismiss} />
) : null}
{actions}
</Animated.View>
</PanGestureHandler>
</Animated.View>

View File

@ -229,7 +229,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
return (
<KeyboardAvoidingView
style={styles.base}
style={[styles.base, {backgroundColor: 'red'}]}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<SafeAreaView
@ -237,14 +237,17 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
edges={hasKeyboard ? ['top'] : ['top', 'bottom']}
>
<ComposeContext.Provider value={{ composeState, composeDispatch }}>
<Stack.Navigator screenOptions={{ headerTopInsetEnabled: false }}>
<Stack.Navigator
screenOptions={{ headerTopInsetEnabled: false }}
initialRouteName='Screen-Compose-Root'
>
<Stack.Screen
name='Tab-Compose-Root'
name='Screen-Compose-Root'
component={ComposeRoot}
options={{ headerLeft, headerCenter, headerRight }}
/>
<Stack.Screen
name='Tab-Compose-EditAttachment'
name='Screen-Compose-EditAttachment'
component={ComposeEditAttachment}
options={{ stackPresentation: 'modal', headerShown: false }}
/>

View File

@ -2,6 +2,7 @@ import client from '@api/client'
import analytics from '@components/analytics'
import haptics from '@components/haptics'
import { HeaderLeft, HeaderRight } from '@components/Header'
import { StackScreenProps } from '@react-navigation/stack'
import React, {
useCallback,
useContext,
@ -18,16 +19,12 @@ import ComposeContext from './utils/createContext'
const Stack = createNativeStackNavigator()
export interface Props {
route: {
params: {
index: number
}
}
navigation: any
}
export type ScreenComposeEditAttachmentProp = StackScreenProps<
Nav.ScreenComposeStackParamList,
'Screen-Compose-EditAttachment'
>
const ComposeEditAttachment: React.FC<Props> = ({
const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
route: {
params: { index }
},

View File

@ -1,14 +1,24 @@
import analytics from '@components/analytics'
import { HeaderRight } from '@components/Header'
import { useActionSheet } from '@expo/react-native-action-sheet'
import { StackScreenProps } from '@react-navigation/stack'
import CameraRoll from '@react-native-community/cameraroll'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { findIndex } from 'lodash'
import React, { useCallback, useLayoutEffect, useState } from 'react'
import { Platform, Share, StyleSheet, Text } from 'react-native'
import { useTranslation } from 'react-i18next'
import {
PermissionsAndroid,
Platform,
Share,
StyleSheet,
Text
} from 'react-native'
import FastImage from 'react-native-fast-image'
import ImageViewer from 'react-native-image-zoom-viewer'
import { SharedElement } from 'react-navigation-shared-element'
import { toast } from '@components/toast'
export type ScreenImagesViewerProp = StackScreenProps<
Nav.RootStackParamList,
@ -27,14 +37,70 @@ const ScreenImagesViewer = React.memo(
findIndex(imageUrls, ['imageIndex', imageIndex])
)
const onPress = useCallback(() => {
analytics('imageviewer_share_press')
switch (Platform.OS) {
case 'ios':
return Share.share({ url: imageUrls[currentIndex].url })
case 'android':
return Share.share({ message: imageUrls[currentIndex].url })
const hasAndroidPermission = async () => {
const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
const hasPermission = await PermissionsAndroid.check(permission)
if (hasPermission) {
return true
}
const status = await PermissionsAndroid.request(permission)
return status === 'granted'
}
const saveImage = async () => {
if (Platform.OS === 'android' && !(await hasAndroidPermission())) {
return
}
CameraRoll.save(
imageUrls[imageIndex].originUrl ||
imageUrls[imageIndex].remote_url ||
imageUrls[imageIndex].preview_url
)
.then(() =>
toast({ type: 'success', message: t('content.save.success') })
)
.catch(() =>
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t('content.save.function')
})
})
)
}
const { t } = useTranslation('screenImageViewer')
const { showActionSheetWithOptions } = useActionSheet()
const onPress = useCallback(() => {
analytics('imageviewer_more_press')
showActionSheetWithOptions(
{
options: [
t('content.options.save'),
t('content.options.share'),
t('content.options.cancel')
],
cancelButtonIndex: 2
},
async buttonIndex => {
switch (buttonIndex) {
case 0:
analytics('imageviewer_more_save_press')
saveImage()
break
case 1:
analytics('imageviewer_more_share_press')
switch (Platform.OS) {
case 'ios':
return Share.share({ url: imageUrls[currentIndex].url })
case 'android':
return Share.share({ message: imageUrls[currentIndex].url })
}
break
}
}
)
}, [currentIndex])
useLayoutEffect(
@ -48,7 +114,11 @@ const ScreenImagesViewer = React.memo(
</Text>
),
headerRight: () => (
<HeaderRight content='Share' native={false} onPress={onPress} />
<HeaderRight
content='MoreHorizontal'
native={false}
onPress={onPress}
/>
)
}),
[currentIndex]
@ -77,6 +147,7 @@ const ScreenImagesViewer = React.memo(
style={{ flex: 1 }}
onChange={index => index !== undefined && setCurrentIndex(index)}
renderImage={renderImage}
onLongPress={saveImage}
/>
)
},

View File

@ -26,7 +26,7 @@ import TabPublic from './Tabs/Public'
export type ScreenTabsParamList = {
'Tab-Local': NavigatorScreenParams<Nav.TabLocalStackParamList>
'Tab-Public': NavigatorScreenParams<Nav.TabPublicStackParamList>
'Tab-Compose': NavigatorScreenParams<Nav.TabComposeStackParamList>
'Tab-Compose': NavigatorScreenParams<Nav.ScreenComposeStackParamList>
'Tab-Notifications': NavigatorScreenParams<Nav.TabNotificationsStackParamList>
'Tab-Me': NavigatorScreenParams<Nav.TabMeStackParamList>
}

View File

@ -8,15 +8,23 @@ const Collections: React.FC = () => {
const { t, i18n } = useTranslation('meRoot')
const navigation = useNavigation()
const { data, isFetching } = useAnnouncementQuery({ showAll: true })
const { data, isFetching } = useAnnouncementQuery({
showAll: true
})
const announcementContent = useMemo(() => {
if (data) {
const amount = data.filter(announcement => !announcement.read).length
if (amount) {
return t('content.collections.announcements.content.unread', { amount })
if (data.length === 0) {
return t('content.collections.announcements.content.empty')
} else {
return t('content.collections.announcements.content.read')
const amount = data.filter(announcement => !announcement.read).length
if (amount) {
return t('content.collections.announcements.content.unread', {
amount
})
} else {
return t('content.collections.announcements.content.read')
}
}
}
}, [data, i18n.language])
@ -49,7 +57,7 @@ const Collections: React.FC = () => {
/>
<MenuRow
iconFront='Clipboard'
iconBack='ChevronRight'
iconBack={data && data.length === 0 ? undefined : 'ChevronRight'}
title={t('content.collections.announcements.heading')}
content={announcementContent}
loading={isFetching}

View File

@ -13,7 +13,10 @@ const ScreenMeSettings: React.FC = () => {
<SettingsTooot />
<SettingsAnalytics />
{__DEV__ || Constants.manifest.releaseChannel?.includes('testing') ? (
{__DEV__ ||
['development'].some(channel =>
Constants.manifest.releaseChannel?.includes(channel)
) ? (
<SettingsDev />
) : null}
</ScrollView>

View File

@ -25,7 +25,7 @@ const SettingsApp: React.FC = () => {
const settingsLanguage = useSelector(getSettingsLanguage)
const settingsTheme = useSelector(getSettingsTheme)
const settingsBrowser = useSelector(getSettingsBrowser)
console.log(settingsLanguage)
return (
<MenuContainer>
<MenuRow
@ -37,7 +37,9 @@ const SettingsApp: React.FC = () => {
i18n.services.resourceStore.data
)
const options = availableLanguages
.map(language => t(`content.language.options.${language}`))
.map(language => {
return t(`content.language.options.${language}`)
})
.concat(t('content.language.options.cancel'))
showActionSheetWithOptions(
@ -47,7 +49,7 @@ const SettingsApp: React.FC = () => {
cancelButtonIndex: options.length - 1
},
buttonIndex => {
if (buttonIndex < options.length) {
if (buttonIndex < options.length - 1) {
analytics('settings_language_press', {
current: i18n.language,
new: availableLanguages[buttonIndex]

View File

@ -6,6 +6,7 @@ import { useSearchQuery } from '@utils/queryHooks/search'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import Constants from 'expo-constants'
import * as Linking from 'expo-linking'
import * as StoreReview from 'expo-store-review'
import * as WebBrowser from 'expo-web-browser'
@ -41,19 +42,30 @@ const SettingsTooot: React.FC = () => {
Linking.openURL('https://www.patreon.com/xmflsct')
}}
/>
{__DEV__ ||
['production', 'development'].some(channel =>
Constants.manifest.releaseChannel?.includes(channel)
) ? (
<MenuRow
title={t('content.review.heading')}
content={
<Icon
name='Star'
size={StyleConstants.Font.Size.M}
color='#FF9500'
/>
}
iconBack='ChevronRight'
onPress={() => {
analytics('settings_review_press')
StoreReview.isAvailableAsync().then(() =>
StoreReview.requestReview()
)
}}
/>
) : null}
<MenuRow
title={t('content.review.heading')}
content={
<Icon name='Star' size={StyleConstants.Font.Size.M} color='#FF9500' />
}
iconBack='ChevronRight'
onPress={() => {
analytics('settings_review_press')
StoreReview.isAvailableAsync().then(() => StoreReview.requestReview())
}}
/>
<MenuRow
title={'联系 tooot'}
title={t('content.contact.heading')}
loading={isLoading}
content={
<Icon

View File

@ -1,5 +1,6 @@
import analytics from '@components/analytics'
import Button from '@components/Button'
import haptics from '@components/haptics'
import ComponentInstance from '@components/Instance'
import { useNavigation } from '@react-navigation/native'
import {
@ -48,6 +49,7 @@ const AccountButton: React.FC<Props> = ({
disabled ? ' ✓' : ''
}`}
onPress={() => {
haptics('Light')
analytics('switch_existing_press')
dispatch(localUpdateActiveIndex(index))
queryClient.clear()
@ -77,14 +79,21 @@ const ScreenMeSwitchRoot: React.FC = () => {
</Text>
<View style={styles.accountButtons}>
{localInstances.length
? localInstances.map((instance, index) => (
<AccountButton
key={index}
index={index}
instance={instance}
disabled={localActiveIndex === index}
/>
))
? localInstances
.slice()
.sort((a, b) =>
`${a.uri}${a.account.acct}`.localeCompare(
`${b.uri}${b.account.acct}`
)
)
.map((instance, index) => (
<AccountButton
key={index}
index={index}
instance={instance}
disabled={localActiveIndex === index}
/>
))
: null}
</View>
</View>

View File

@ -1,21 +1,11 @@
import analytics from '@components/analytics'
import { HeaderRight } from '@components/Header'
import Timeline from '@components/Timelines/Timeline'
import HeaderActionsAccount from '@components/Timelines/Timeline/Shared/HeaderActions/Account'
import HeaderActionsShare from '@components/Timelines/Timeline/Shared/HeaderActions/Share'
import { useAccountQuery } from '@utils/queryHooks/account'
import { getLocalAccount } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import React, {
useCallback,
useEffect,
useMemo,
useReducer,
useState
} from 'react'
import React, { useCallback, useEffect, useMemo, useReducer } from 'react'
import { StyleSheet, View } from 'react-native'
import { useSharedValue } from 'react-native-reanimated'
import { useSelector } from 'react-redux'
import AccountAttachments from './Account/Attachments'
import AccountHeader from './Account/Header'
import AccountInformation from './Account/Information'
@ -33,7 +23,6 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
}) => {
const { theme } = useTheme()
const localAccount = useSelector(getLocalAccount)
const { data } = useAccountQuery({ id: account.id })
const scrollY = useSharedValue(0)
@ -42,7 +31,6 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
accountInitialState
)
const [modalVisible, setBottomSheetVisible] = useState(false)
useEffect(() => {
const updateHeaderRight = () =>
navigation.setOptions({
@ -53,7 +41,10 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
analytics('bottomsheet_open_press', {
page: 'account'
})
setBottomSheetVisible(true)
navigation.navigate('Screen-Actions', {
type: 'account',
account
})
}}
/>
)
@ -89,25 +80,6 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
ListHeaderComponent
}}
/>
{/* <BottomSheet
visible={modalVisible}
handleDismiss={() => setBottomSheetVisible(false)}
>
{localAccount?.id !== account.id && (
<HeaderActionsAccount
account={account}
setBottomSheetVisible={setBottomSheetVisible}
/>
)}
<HeaderActionsShare
url={account.url}
type='account'
setBottomSheetVisible={setBottomSheetVisible}
/>
</BottomSheet> */}
</AccountContext.Provider>
)
}

View File

@ -5,7 +5,6 @@ import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { useTimelineQuery } from '@utils/queryHooks/timeline'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect } from 'react'
import {
@ -29,6 +28,8 @@ const AccountAttachments = React.memo(
>()
const { theme } = useTheme()
const DISPLAY_AMOUNT = 6
const width =
(Dimensions.get('screen').width -
StyleConstants.Spacing.Global.PagePadding * 2) /
@ -38,7 +39,7 @@ const AccountAttachments = React.memo(
page: 'Account_Attachments' as 'Account_Attachments',
account: account?.id
}
const { data, refetch } = useTimelineQuery({
const { data, refetch } = useTimelineQuery<Mastodon.Status[]>({
...queryKeyParams,
options: { enabled: false }
})
@ -48,18 +49,16 @@ const AccountAttachments = React.memo(
}
}, [account])
const flattenData = (data?.pages
? data.pages.flatMap(d => [...d])
: []) as Mastodon.Status[]
useEffect(() => {
if (flattenData.length) {
layoutAnimation()
}
}, [flattenData.length])
const flattenData = data?.pages
? data.pages
.flatMap(d => [...d])
.filter(status => !status.sensitive)
.splice(0, DISPLAY_AMOUNT)
: []
const renderItem = useCallback<ListRenderItem<Mastodon.Status>>(
({ item, index }) => {
if (index === 3) {
if (index === DISPLAY_AMOUNT - 1) {
return (
<Pressable
onPress={() => {
@ -128,7 +127,7 @@ const AccountAttachments = React.memo(
<Animated.View style={[styles.base, styleContainer]}>
<FlatList
horizontal
data={flattenData.filter(status => !status.sensitive).splice(0, 4)}
data={flattenData}
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
/>

View File

@ -66,7 +66,7 @@ const AccountInformationName: React.FC<Props> = ({ account }) => {
const styles = StyleSheet.create({
base: {
borderRadius: 0,
marginTop: StyleConstants.Spacing.M,
marginTop: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.XS
},
moved: {

View File

@ -8,7 +8,11 @@ const sentry = () => {
environment: Constants.manifest.extra.sentryEnv,
dsn: Constants.manifest.extra.sentryDSN,
enableInExpoDevelopment: false,
debug: __DEV__
debug:
__DEV__ ||
['development'].some(channel =>
Constants.manifest.releaseChannel?.includes(channel)
)
})
}

View File

@ -12,7 +12,7 @@ import { persistReducer, persistStore } from 'redux-persist'
const secureStorage = createSecureStore()
const prefix = 'ajieorjaiojwoirjwe'
const prefix = 'tooot'
const contextsPersistConfig = {
key: 'contexts',

View File

@ -2,16 +2,30 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import * as Analytics from 'expo-firebase-analytics'
import * as Localization from 'expo-localization'
import { pickBy } from 'lodash'
enum availableLanguages {
'zh-Hans',
'en'
}
export type SettingsState = {
language: 'zh-Hans' | 'en'
language: keyof availableLanguages
theme: 'light' | 'dark' | 'auto'
browser: 'internal' | 'external'
analytics: boolean
}
export const settingsInitialState = {
language: Localization.locale,
language: Object.keys(
pickBy(availableLanguages, (_, key) => Localization.locale.includes(key))
)
? Object.keys(
pickBy(availableLanguages, (_, key) =>
Localization.locale.includes(key)
)
)[0]
: 'en',
theme: 'auto',
browser: 'internal',
analytics: true

View File

@ -21,5 +21,5 @@ export const StyleConstants = {
Global: { PagePadding: Base * 4 }
},
Avatar: { S: 40, M: 52, L: 104 }
Avatar: { S: 40, M: 52, L: 96 }
}

View File

@ -1934,6 +1934,11 @@
dependencies:
prop-types "^15.5.10"
"@react-native-community/cameraroll@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@react-native-community/cameraroll/-/cameraroll-4.0.2.tgz#5baac97f4a56f50b7307b08efcc1fe37acdec462"
integrity sha512-GtSZO6pqUzyZvaYidB5zH90o6Yb9YatapgiMQ+JVdbK4bDD74GdrNGDwyinDTzE5LkAQ90HDoAhVgV/uWt5OrQ==
"@react-native-community/cli-debugger-ui@^4.13.1":
version "4.13.1"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-4.13.1.tgz#07de6d4dab80ec49231de1f1fbf658b4ad39b32c"