Preparing for CI test

This commit is contained in:
Zhiyuan Zheng 2021-02-02 22:50:38 +01:00
parent b8aa402c99
commit 1a5b21d56a
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
54 changed files with 641 additions and 448 deletions

View File

@ -1,31 +0,0 @@
name: Publish development
on:
push:
branches:
- '*-development'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: -- Step 1 -- Checkout code
uses: actions/checkout@v2
- name: -- Step 2 -- Setup node
uses: actions/setup-node@v2
with:
node-version: 14.x
- name: -- Step 3 -- Use Expo action
uses: expo/expo-github-action@v5
with:
expo-version: 4.x
expo-username: ${{ secrets.EXPO_USERNAME }}
expo-token: ${{ secrets.EXPO_TOKEN }}
- name: -- Step 4 -- Install dependencies
run: yarn install
- name: -- Step 5 -- Publish
env:
SENTRY_ORGANIZATION: ${{ secrets.SENTRY_ORGANIZATION }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_DEPLOY_ENV: development
run: expo publish --release-channel=${GITHUB_REF#refs/heads/}

View File

@ -2,7 +2,7 @@ name: Publish production
on: on:
push: push:
branches: branches:
- '*-production' - production
jobs: jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -1,11 +1,11 @@
name: Publish staging name: Build staging
on: on:
push: push:
branches: branches:
- '*-staging' - staging
jobs: jobs:
publish: build-ios:
runs-on: ubuntu-latest runs-on: macos-latest
steps: steps:
- name: -- Step 1 -- Checkout code - name: -- Step 1 -- Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -13,19 +13,26 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 14.x node-version: 14.x
- name: -- Step 3 -- Use Expo action - name: -- Step 3 -- Setup ruby
uses: expo/expo-github-action@v5 uses: actions/setup-ruby@v1
with:
expo-version: 4.x
expo-username: ${{ secrets.EXPO_USERNAME }}
expo-token: ${{ secrets.EXPO_TOKEN }}
- name: -- Step 4 -- Install dependencies - name: -- Step 4 -- Install dependencies
run: yarn install run: yarn install
- name: -- Step 5 -- Publish - name: -- Step 5 -- Install native dependencies
run: npx pod-install
- name: -- Step 6 -- Run fastlane
env: env:
TOOOT_ENVIRONMENT: staging
SENTRY_ORGANIZATION: ${{ secrets.SENTRY_ORGANIZATION }} SENTRY_ORGANIZATION: ${{ secrets.SENTRY_ORGANIZATION }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_DEPLOY_ENV: staging LC_ALL: en_US.UTF-8
run: expo publish --release-channel=${GITHUB_REF#refs/heads/} LANG: en_US.UTF-8
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
MATCH_USERNAME: ${{ secrets.MATCH_USERNAME }}
MATCH_GIT_PRIVATE_KEY: ${{ MATCH_GIT_PRIVATE_KEY }}
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ APP_STORE_CONNECT_API_KEY_KEY_ID }}
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_KEY: ${{ APP_STORE_CONNECT_API_KEY_KEY }}
run: yarn ios:build

View File

@ -1,3 +1,5 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "fastlane" gem "fastlane"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

View File

@ -85,6 +85,9 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0) xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0) xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3) xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-json (1.0.0)
fastlane-plugin-versioning (0.4.4)
fastlane-plugin-yarn (1.2)
gh_inspector (1.1.3) gh_inspector (1.1.3)
google-api-client (0.38.0) google-api-client (0.38.0)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
@ -196,6 +199,9 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
fastlane fastlane
fastlane-plugin-json
fastlane-plugin-versioning
fastlane-plugin-yarn
BUNDLED WITH BUNDLED WITH
1.17.2 1.17.2

30
VERSIONING.md Normal file
View File

@ -0,0 +1,30 @@
## Major releases - App Store
"Major releases" are artifacts published as `x.?.?`:
* An artifact must be released as `x.?.?` if native modules have been changed or updated, including upgrading Expo SDK version.
* A new app store version has to be submitted.
* Outdated versions in principle do not receive further OTA updates.
## Minor releases - App Store and OTA
"Minor releases" are artifacts published as `?.y.?`:
* An artifact can be released as `?.y.?` when there is no change nor update made to the native modules.
* A new app store version can be submitted for better first launch experience.
* All these versions that are not part of above mentioned outdates versions receive also OTA updates.
## Patch releases - OTA
"Patch releases" are artifacts published as `?.?.z`:
* An artifact must be release as `?.?.z` when there is no major change to the functionalities.
* No new app store version will be submitted.
* All these versions that are not part of above mentioned outdates versions receive also OTA updates.
## OTA release channels
* `MAJOR-environment`. Environments include `production`, `staging` and `development`.
## Major versions mapping to native module versions
| Major version | Native module version |
| :-----------: | :-------------------: |
| `0` | `210201` |

View File

@ -21,13 +21,28 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme"> <application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:theme="@style/AppTheme"
>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@xmflsct/tooot"/> <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@xmflsct/tooot"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="40.0.0"/> <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="40.0.0"/>
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/> <meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/> <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/> <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait"> <activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.App.SplashScreen"
android:screenOrientation="portrait"
>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@ -42,6 +57,6 @@
</activity> </activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
</application> </application>
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application android:requestLegacyExternalStorage="true"/> <application android:requestLegacyExternalStorage="true"/>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually -->
<color name="splashscreen_background">#191919</color>
</resources>

View File

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml
version="1.0"
encoding="UTF-8"
standalone="yes"
?>
<resources> <resources>
<!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually -->
<color name="iconBackground">#FFFFFF</color> <color name="iconBackground">#FFFFFF</color>
<color name="splashscreen_background">#FAFAFA</color> <color name="splashscreen_background">#FAFAFA</color>
<color name="colorPrimary">#023c69</color> <color name="colorPrimary">#023c69</color>
</resources> </resources>

View File

@ -11,7 +11,8 @@
</style> </style>
<style name="Theme.App.SplashScreen" parent="Theme.AppCompat.Light.NoActionBar"> <style name="Theme.App.SplashScreen" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually --> <!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually -->
<!-- Customize your splash screen theme here -->
<item name="android:windowBackground">@drawable/splashscreen</item> <item name="android:windowBackground">@drawable/splashscreen</item>
<item name="android:windowFullscreen">true</item>
<!-- Customize your splash screen theme here -->
</style> </style>
</resources> </resources>

View File

@ -1,26 +1,20 @@
import { ExpoConfig } from '@expo/config' import { ExpoConfig } from '@expo/config'
import { versions } from './package.json'
import 'dotenv/config' import 'dotenv/config'
const toootVersion = `${versions.major}.${versions.minor}.${versions.patch}`
export default (): ExpoConfig => ({ export default (): ExpoConfig => ({
name: 'tooot', name: 'tooot',
description: 'tooot for Mastodon', description: 'tooot for Mastodon',
slug: 'tooot', slug: 'tooot',
version: toootVersion,
sdkVersion: versions.expo,
privacy: 'hidden', privacy: 'hidden',
sdkVersion: '40.0.0',
version: '0.8',
platforms: ['ios', 'android'],
orientation: 'portrait',
userInterfaceStyle: 'automatic',
icon: './assets/icon.png',
splash: {
backgroundColor: '#FAFAFA',
image: './assets/splash.png'
},
scheme: 'tooot',
assetBundlePatterns: ['assets/*'], assetBundlePatterns: ['assets/*'],
extra: { extra: {
sentryDSN: process.env.SENTRY_DSN, toootEnvironment: process.env.TOOOT_ENVIRONMENT,
sentryEnv: process.env.SENTRY_DEPLOY_ENV sentryDSN: process.env.SENTRY_DSN
}, },
hooks: { hooks: {
postPublish: [ postPublish: [
@ -31,7 +25,7 @@ export default (): ExpoConfig => ({
project: process.env.SENTRY_PROJECT, project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN, authToken: process.env.SENTRY_AUTH_TOKEN,
setCommits: process.env.GITHUB_SHA || undefined, setCommits: process.env.GITHUB_SHA || undefined,
deployEnv: process.env.SENTRY_DEPLOY_ENV deployEnv: process.env.TOOOT_ENVIRONMENT
} }
} }
] ]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,79 +1,110 @@
$ExpoSDK = '40.0.0' fastlane_version "2.172.0"
$NativeVersion = '210201' # Update when there is native module change
fastlane_version '2.172.0' ensure_env_vars(
env_vars: ["TOOOT_ENVIRONMENT"]
)
VERSIONS = read_json( json_path: "./package.json" )[:versions]
ENVIRONMENT = ENV["TOOOT_ENVIRONMENT"]
VERSION = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}"
RELEASE_CHANNEL = "#{VERSIONS[:major]}-#{ENVIRONMENT}"
BUILD_NUMBER = Time.now.strftime("%y%m%d")
platform :ios do platform :ios do
desc 'Build and deploy' XCODEPROJ = "./ios/tooot.xcodeproj"
private_lane :build do |options| INFO_PLIST = "./ios/tooot/Info.plist"
branch = 'NATIVEVERSION-TYPE'.gsub('NATIVEVERSION', $NativeVersion).gsub('TYPE', options[:type]) EXPO_PLIST = "./ios/tooot/Supporting/Expo.plist"
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] desc "Prepare app store"
when 'staging', 'production' private_lane :prepare_appstore do
ensure_git_branch( case ENVIRONMENT
branch: options[:type] when "staging", "production"
) increment_build_number( xcodeproj: XCODEPROJ, build_number: BUILD_NUMBER )
ensure_git_status_clean app_store_connect_api_key
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]
when 'development'
build_ios_app(
scheme: 'tooot',
silent: true,
include_bitcode: true,
workspace: './ios/tooot.xcworkspace',
export_method: 'development'
)
install_on_device(
skip_wifi: true
)
when 'staging'
build_ios_app(
scheme: 'tooot',
workspace: './ios/tooot.xcworkspace'
)
upload_to_testflight(
skip_submission: true,
notify_external_testers: false
)
end end
end end
desc 'Build development to phone' desc "Expo release"
lane :development do private_lane :expo_release do
build(type: 'development') yarn( package_path: "./package.json", flags: "release", command: RELEASE_CHANNEL )
end end
desc 'Build staging to TestFlight' desc "Get certificates"
lane :staging do private_lane :get_certificates do |options|
build(type: 'staging') if ENV['CI'] == true
match( type: options[:type], readonly: true, keychain_name: KEYCHAIN_NAME, keychain_password: KEYCHAIN_PASS )
else
match( type: options[:type], readonly: true )
end
end end
desc 'Build product to App Store' desc "Build and deploy"
lane :production do lane :build do
build(type: 'production') BUILD_DIRECTORY = "./ios/build"
SHOULD_BUILD_NATIVE = false
case ENVIRONMENT
when "staging", "production"
PREVIOUS_VERSION = get_info_plist_value( path: INFO_PLIST, key: "CFBundleShortVersionString" )
if VERSION.to_f > PREVIOUS_VERSION.to_f
SHOULD_BUILD_NATIVE = true
set_info_plist_value( path: INFO_PLIST, key: "CFBundleShortVersionString", value: VERSION )
set_info_plist_value( path: EXPO_PLIST, key: "EXUpdatesSDKVersion", value: VERSIONS[:expo] )
set_info_plist_value( path: EXPO_PLIST, key: "EXUpdatesReleaseChannel", value: RELEASE_CHANNEL )
end
when "development"
SHOULD_BUILD_NATIVE = true
end
if SHOULD_BUILD_NATIVE == true
prepare_appstore
KEYCHAIN_NAME = "tooot"
KEYCHAIN_PASS = SecureRandom.base64
if ENV['CI'] == true
create_keychain(
name: KEYCHAIN_NAME,
password: KEYCHAIN_PASS,
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: true
)
end
case ENVIRONMENT
when "development"
get_certificates( type: "development" )
build_ios_app(
export_method: "development",
output_directory: BUILD_DIRECTORY,
output_name: VERSION + "-" + BUILD_NUMBER
)
install_on_device( skip_wifi: true )
when "staging"
get_certificates( type: "appstore" )
build_ios_app(
export_method: "app-store",
output_directory: BUILD_DIRECTORY,
output_name: VERSION + "-" + BUILD_NUMBER
)
upload_to_testflight(
demo_account_required: true,
distribute_external: true,
groups: "内测用户",
changelog: "Ready for testing"
)
when "production"
get_certificates( type: "appstore" )
build_ios_app(
export_method: "app-store",
output_directory: BUILD_DIRECTORY,
output_name: VERSION + "-" + BUILD_NUMBER
)
end
end
expo_release
end end
end end

3
fastlane/Gymfile Normal file
View File

@ -0,0 +1,3 @@
scheme "tooot"
workspace "./ios/tooot.xcworkspace"
clean true

View File

@ -1,3 +1,2 @@
git_user_email("me@xmflsct.com") git_user_email("me@xmflsct.com")
git_private_key("./github.key")
storage_mode("git") storage_mode("git")

6
fastlane/Pluginfile Normal file
View File

@ -0,0 +1,6 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-yarn'
gem 'fastlane-plugin-json'

View File

@ -16,21 +16,11 @@ or alternatively using `brew install fastlane`
# Available Actions # Available Actions
## iOS ## iOS
### ios development ### ios build
``` ```
fastlane ios development fastlane ios build
``` ```
Build development to phone Build and deploy
### ios staging
```
fastlane ios staging
```
Build staging to TestFlight
### ios production
```
fastlane ios production
```
Build product to App Store
---- ----

View File

@ -12,6 +12,7 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
5E36538325C9B8BD009F93EE /* RootViewColor.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E36538225C9B8BD009F93EE /* RootViewColor.xcassets */; };
6CB3B7B773184F6EB8040C3E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C2DAF0391E246238BE2A4B4 /* InfoPlist.strings */; }; 6CB3B7B773184F6EB8040C3E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C2DAF0391E246238BE2A4B4 /* InfoPlist.strings */; };
8BA74ECC129842FEA0CC08AF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F78D778B9BBC48D584012340 /* InfoPlist.strings */; }; 8BA74ECC129842FEA0CC08AF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F78D778B9BBC48D584012340 /* InfoPlist.strings */; };
96905EF65AED1B983A6B3ABC /* libPods-tooot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */; }; 96905EF65AED1B983A6B3ABC /* libPods-tooot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */; };
@ -30,6 +31,7 @@
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = tooot/main.m; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = tooot/main.m; sourceTree = "<group>"; };
4C2DAF0391E246238BE2A4B4 /* InfoPlist.strings */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = /Users/zhzhe/Documents/GitHub/tooot/app/ios/tooot/Supporting/en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 4C2DAF0391E246238BE2A4B4 /* InfoPlist.strings */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = /Users/zhzhe/Documents/GitHub/tooot/app/ios/tooot/Supporting/en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-tooot.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-tooot.a"; sourceTree = BUILT_PRODUCTS_DIR; };
5E36538225C9B8BD009F93EE /* RootViewColor.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = RootViewColor.xcassets; path = tooot/RootViewColor.xcassets; sourceTree = "<group>"; };
6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.debug.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.debug.xcconfig"; sourceTree = "<group>"; }; 6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.debug.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.debug.xcconfig"; sourceTree = "<group>"; };
7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.release.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.release.xcconfig"; sourceTree = "<group>"; }; 7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-tooot.release.xcconfig"; path = "Target Support Files/Pods-tooot/Pods-tooot.release.xcconfig"; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = "<group>"; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = "<group>"; };
@ -72,6 +74,7 @@
13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */, 13B07FB71A68108700A75B9A /* main.m */,
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
5E36538225C9B8BD009F93EE /* RootViewColor.xcassets */,
B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */, B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */,
); );
name = tooot; name = tooot;
@ -179,6 +182,7 @@
13B07F861A680F5B00A75B9A = { 13B07F861A680F5B00A75B9A = {
DevelopmentTeam = 8EGBLQ2MA6; DevelopmentTeam = 8EGBLQ2MA6;
LastSwiftMigration = 1120; LastSwiftMigration = 1120;
ProvisioningStyle = Automatic;
}; };
}; };
}; };
@ -205,6 +209,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5E36538325C9B8BD009F93EE /* RootViewColor.xcassets in Resources */,
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
@ -325,7 +330,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements; CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
CURRENT_PROJECT_VERSION = 1; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2102022230;
DEVELOPMENT_TEAM = 8EGBLQ2MA6; DEVELOPMENT_TEAM = 8EGBLQ2MA6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@ -343,6 +350,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot; PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot;
PRODUCT_NAME = tooot; PRODUCT_NAME = tooot;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1; TARGETED_DEVICE_FAMILY = 1;
@ -357,7 +365,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements; CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements;
CURRENT_PROJECT_VERSION = 1; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2102022230;
DEVELOPMENT_TEAM = 8EGBLQ2MA6; DEVELOPMENT_TEAM = 8EGBLQ2MA6;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = tooot/Info.plist; INFOPLIST_FILE = tooot/Info.plist;
@ -370,6 +380,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot; PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot;
PRODUCT_NAME = tooot; PRODUCT_NAME = tooot;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1; TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";

View File

@ -85,7 +85,7 @@ static void InitializeFlipper(UIApplication *application) {
{ {
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions]; RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; rootView.backgroundColor = [UIColor colorNamed:@"Background"];
UIViewController *rootViewController = [UIViewController new]; UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView; rootViewController.view = rootView;

View File

@ -5,11 +5,42 @@
"filename": "splashscreen.png", "filename": "splashscreen.png",
"scale": "1x" "scale": "1x"
}, },
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"filename": "dark_splashscreen.png",
"scale": "1x"
},
{ {
"idiom": "universal", "idiom": "universal",
"scale": "2x" "scale": "2x"
}, },
{ {
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"scale": "2x"
},
{
"idiom": "universal",
"scale": "3x"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal", "idiom": "universal",
"scale": "3x" "scale": "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -5,11 +5,42 @@
"filename": "background.png", "filename": "background.png",
"scale": "1x" "scale": "1x"
}, },
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"filename": "dark_background.png",
"scale": "1x"
},
{ {
"idiom": "universal", "idiom": "universal",
"scale": "2x" "scale": "2x"
}, },
{ {
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"scale": "2x"
},
{
"idiom": "universal",
"scale": "3x"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal", "idiom": "universal",
"scale": "3x" "scale": "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

View File

@ -1,95 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleAllowMixedLocalizations</key>
<string>en</string> <true/>
<key>CFBundleExecutable</key> <key>CFBundleDevelopmentRegion</key>
<string>$(EXECUTABLE_NAME)</string> <string>en</string>
<key>CFBundleIdentifier</key> <key>CFBundleDisplayName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>tooot</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleExecutable</key>
<string>6.0</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleName</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_NAME)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundlePackageType</key> <key>CFBundleInfoDictionaryVersion</key>
<string>APPL</string> <string>6.0</string>
<key>CFBundleShortVersionString</key> <key>CFBundleName</key>
<string>0.1.0</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundleSignature</key> <key>CFBundlePackageType</key>
<string>????</string> <string>APPL</string>
<key>CFBundleVersion</key> <key>CFBundleShortVersionString</key>
<string>0</string> <string>0.8</string>
<key>LSRequiresIPhoneOS</key> <key>CFBundleSignature</key>
<true/> <string>????</string>
<key>NSAppTransportSecurity</key> <key>CFBundleURLTypes</key>
<dict> <array>
<key>NSAllowsArbitraryLoads</key> <dict>
<true/> <key>CFBundleURLName</key>
<key>NSExceptionDomains</key> <string>gizmos</string>
<dict> <key>CFBundleURLSchemes</key>
<key>localhost</key> <array>
<dict> <string>tooot</string>
<key>NSExceptionAllowsInsecureHTTPLoads</key> </array>
<true/> </dict>
</dict> </array>
</dict> <key>CFBundleVersion</key>
</dict> <string>2102022230</string>
<key>NSLocationWhenInUseUsageDescription</key> <key>ITSAppUsesNonExemptEncryption</key>
<string/> <false/>
<key>UILaunchStoryboardName</key> <key>LSRequiresIPhoneOS</key>
<string>SplashScreen</string> <true/>
<key>UIRequiredDeviceCapabilities</key> <key>NSAppTransportSecurity</key>
<array> <dict>
<string>armv7</string> <key>NSAllowsArbitraryLoads</key>
</array> <true/>
<key>UISupportedInterfaceOrientations</key> <key>NSExceptionDomains</key>
<array> <dict>
<string>UIInterfaceOrientationPortrait</string> <key>localhost</key>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <dict>
</array> <key>NSExceptionAllowsInsecureHTTPLoads</key>
<key>UIViewControllerBasedStatusBarAppearance</key> <true/>
<false/> </dict>
<key>CFBundleAllowMixedLocalizations</key> </dict>
<true/> </dict>
<key>ITSAppUsesNonExemptEncryption</key> <key>NSCameraUsageDescription</key>
<false/> <string>Give $(PRODUCT_NAME) permission to access your camera</string>
<key>UIUserInterfaceStyle</key> <key>NSLocationWhenInUseUsageDescription</key>
<string>Automatic</string> <string/>
<key>CFBundleURLTypes</key> <key>NSMicrophoneUsageDescription</key>
<array> <string>Give $(PRODUCT_NAME) permission to use your microphone</string>
<dict> <key>NSPhotoLibraryAddUsageDescription</key>
<key>CFBundleURLSchemes</key> <string>Give $(PRODUCT_NAME) permission to save photos</string>
<array> <key>NSPhotoLibraryUsageDescription</key>
<string>tooot</string> <string>Give $(PRODUCT_NAME) permission to save photos</string>
<string>com.xmflsct.app.tooot</string> <key>UILaunchStoryboardName</key>
</array> <string>SplashScreen</string>
</dict> <key>UIRequiredDeviceCapabilities</key>
</array> <array>
<key>UIRequiresFullScreen</key> <string>armv7</string>
<true/> </array>
<key>CFBundleDisplayName</key> <key>UIRequiresFullScreen</key>
<string>tooot</string> <true/>
<key>NSMicrophoneUsageDescription</key> <key>UIStatusBarHidden</key>
<string>Allow $(PRODUCT_NAME) to access your microphone</string> <true/>
<key>NSPhotoLibraryUsageDescription</key> <key>UISupportedInterfaceOrientations</key>
<string>Give $(PRODUCT_NAME) permission to save photos</string> <array>
<key>NSPhotoLibraryAddUsageDescription</key> <string>UIInterfaceOrientationPortrait</string>
<string>Give $(PRODUCT_NAME) permission to save photos</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<key>NSCameraUsageDescription</key> </array>
<string>Give $(PRODUCT_NAME) permission to access your camera</string> <key>UIUserInterfaceStyle</key>
<key>NSMicrophoneUsageDescription</key> <string>Automatic</string>
<string>Give $(PRODUCT_NAME) permission to use your microphone</string> <key>UIViewControllerBasedStatusBarAppearance</key>
<key>CFBundleURLTypes</key> <false/>
<array> </dict>
<dict> </plist>
<key>CFBundleURLName</key>
<string>gizmos</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tooot</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "250",
"green" : "250",
"red" : "250"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "25",
"green" : "25",
"red" : "25"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

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>210201-development</string> <string>0-staging</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

@ -3,7 +3,7 @@
"start": "react-native start", "start": "react-native start",
"android": "react-native run-android", "android": "react-native run-android",
"ios": "react-native run-ios", "ios": "react-native run-ios",
"ios:development": "bundle exec fastlane ios development", "ios:build": "bundle exec fastlane ios build",
"test": "jest --watchAll", "test": "jest --watchAll",
"release": "scripts/release.sh" "release": "scripts/release.sh"
}, },
@ -104,6 +104,12 @@
"typescript": "~4.1.3" "typescript": "~4.1.3"
}, },
"private": true, "private": true,
"name": "app", "name": "tooot",
"version": "1.0.0" "versions": {
} "native": "210201",
"major": 0,
"minor": 8,
"patch": 0,
"expo": "40.0.0"
}
}

BIN
splashes/dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -114,14 +114,18 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
return ( return (
<> <>
<StatusBar barStyle={barStyle[mode]} backgroundColor={theme.background} /> <StatusBar barStyle={barStyle[mode]} />
<NavigationContainer <NavigationContainer
ref={navigationRef} ref={navigationRef}
theme={themes[mode]} theme={themes[mode]}
onReady={navigationContainerOnReady} onReady={navigationContainerOnReady}
onStateChange={navigationContainerOnStateChange} onStateChange={navigationContainerOnStateChange}
> >
<Stack.Navigator mode='modal' initialRouteName='Screen-Tabs'> <Stack.Navigator
mode='modal'
initialRouteName='Screen-Tabs'
screenOptions={{ cardStyle: { backgroundColor: theme.background } }}
>
<Stack.Screen <Stack.Screen
name='Screen-Tabs' name='Screen-Tabs'
component={ScreenTabs} component={ScreenTabs}

View File

@ -1,14 +1,8 @@
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import React, { useCallback, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { import { Pressable, StyleProp, StyleSheet, ViewStyle } from 'react-native'
ImageStyle,
Pressable,
StyleProp,
StyleSheet,
ViewStyle
} from 'react-native'
import { Blurhash } from 'react-native-blurhash' import { Blurhash } from 'react-native-blurhash'
import FastImage from 'react-native-fast-image' import FastImage, { ImageStyle } from 'react-native-fast-image'
import { SharedElement } from 'react-navigation-shared-element' import { SharedElement } from 'react-navigation-shared-element'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -23,70 +17,120 @@ export interface Props {
imageStyle?: StyleProp<ImageStyle> imageStyle?: StyleProp<ImageStyle>
} }
const GracefullyImage: React.FC<Props> = ({ const GracefullyImage = React.memo(
sharedElement, ({
hidden = false, sharedElement,
uri, hidden = false,
blurhash, uri,
dimension, blurhash,
onPress, dimension,
style, onPress,
imageStyle style,
}) => { imageStyle
const { mode, theme } = useTheme() }: Props) => {
const [imageLoaded, setImageLoaded] = useState(false) const { mode, theme } = useTheme()
const [previewLoaded, setPreviewLoaded] = useState(
const children = useCallback(() => { uri.preview ? false : true
return (
<>
{sharedElement ? (
<SharedElement id={`image.${sharedElement}`} style={[styles.image]}>
<FastImage
source={{ uri: uri.preview || uri.original || uri.remote }}
style={[styles.image, imageStyle]}
onLoad={() => setImageLoaded(true)}
/>
</SharedElement>
) : (
<FastImage
source={{ uri: uri.preview || uri.original || uri.remote }}
style={[styles.image, imageStyle]}
onLoad={() => setImageLoaded(true)}
/>
)}
{blurhash && (hidden || !imageLoaded) ? (
<Blurhash
decodeAsync
blurhash={blurhash}
style={{
width: '100%',
height: '100%',
position: 'absolute',
top: StyleConstants.Spacing.XS / 2,
left: StyleConstants.Spacing.XS / 2
}}
/>
) : null}
</>
) )
}, [hidden, imageLoaded, mode, uri]) const [originalLoaded, setOriginalLoaded] = useState(false)
const [originalFailed, setOriginalFailed] = useState(false)
const [remoteLoaded, setRemoteLoaded] = useState(uri.remote ? false : true)
return ( const sourceUri = useMemo(() => {
<Pressable if (previewLoaded) {
children={children} if (originalFailed) {
style={[ return uri.remote
style, } else {
{ backgroundColor: theme.shimmerDefault }, return uri.original
dimension && { ...dimension } }
]} } else {
{...(onPress return uri.preview
? hidden }
? { disabled: true } }, [previewLoaded, originalLoaded, originalFailed, remoteLoaded])
: { onPress } const onLoad = useCallback(() => {
: { disabled: true })} if (previewLoaded) {
/> if (originalFailed) {
) return setRemoteLoaded(true)
} } else {
return setOriginalLoaded(true)
}
} else {
return setPreviewLoaded(true)
}
}, [previewLoaded, originalLoaded, originalFailed, remoteLoaded])
const onError = useCallback(() => {
if (previewLoaded) {
if (originalFailed) {
return
} else {
return setOriginalFailed(true)
}
} else {
return
}
}, [previewLoaded, originalLoaded, originalFailed, remoteLoaded])
const children = useCallback(() => {
return (
<>
{sharedElement ? (
<SharedElement id={`image.${sharedElement}`} style={[styles.image]}>
<FastImage
source={{ uri: sourceUri }}
style={[styles.image, imageStyle]}
onLoad={onLoad}
onError={onError}
/>
</SharedElement>
) : (
<FastImage
source={{ uri: sourceUri }}
style={[styles.image, imageStyle]}
onLoad={onLoad}
onError={onError}
/>
)}
{blurhash &&
(hidden || !(previewLoaded || originalLoaded || remoteLoaded)) ? (
<Blurhash
decodeAsync
blurhash={blurhash}
style={{
width: '100%',
height: '100%',
position: 'absolute',
top: StyleConstants.Spacing.XS / 2,
left: StyleConstants.Spacing.XS / 2
}}
/>
) : null}
</>
)
}, [hidden, previewLoaded, originalLoaded, remoteLoaded, mode, uri])
return (
<Pressable
children={children}
style={[
style,
{ backgroundColor: theme.shimmerDefault },
dimension && { ...dimension }
]}
{...(onPress
? hidden
? { disabled: true }
: { onPress }
: { disabled: true })}
/>
)
},
(prev, next) => {
let skipUpdate = true
skipUpdate = prev.hidden === next.hidden
skipUpdate = prev.uri.original === next.uri.original
return false
}
)
const styles = StyleSheet.create({ const styles = StyleSheet.create({
image: { image: {

View File

@ -202,6 +202,7 @@ const TimelineActions: React.FC<Props> = ({
: iconColorAction(status.reblogged) : iconColorAction(status.reblogged)
} }
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
strokeWidth={status.reblogged ? 3 : undefined}
/> />
{status.reblogs_count > 0 && ( {status.reblogs_count > 0 && (
<Text <Text
@ -225,6 +226,7 @@ const TimelineActions: React.FC<Props> = ({
name='Heart' name='Heart'
color={iconColorAction(status.favourited)} color={iconColorAction(status.favourited)}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
strokeWidth={status.favourited ? 3 : undefined}
/> />
{status.favourites_count > 0 && ( {status.favourites_count > 0 && (
<Text <Text
@ -248,6 +250,7 @@ const TimelineActions: React.FC<Props> = ({
name='Bookmark' name='Bookmark'
color={iconColorAction(status.bookmarked)} color={iconColorAction(status.bookmarked)}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
strokeWidth={status.bookmarked ? 3 : undefined}
/> />
), ),
[status.bookmarked] [status.bookmarked]

View File

@ -114,7 +114,7 @@ const TimelineAttachment: React.FC<Props> = ({ status }) => {
return ( return (
<View> <View>
<View style={styles.container}>{attachments}</View> <View style={styles.container} children={attachments} />
{status.sensitive && {status.sensitive &&
(sensitiveShown ? ( (sensitiveShown ? (

View File

@ -1,3 +1,4 @@
import analytics from '@components/analytics'
import Button from '@components/Button' import Button from '@components/Button'
import GracefullyImage from '@components/GracefullyImage' import GracefullyImage from '@components/GracefullyImage'
import { Slider } from '@sharcoux/slider' import { Slider } from '@sharcoux/slider'
@ -8,7 +9,6 @@ import React, { useCallback, useState } from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import { Blurhash } from 'react-native-blurhash' import { Blurhash } from 'react-native-blurhash'
import attachmentAspectRatio from './aspectRatio' import attachmentAspectRatio from './aspectRatio'
import analytics from '@components/analytics'
export interface Props { export interface Props {
total: number total: number

View File

@ -27,6 +27,7 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
uri={{ original: card.image }} uri={{ original: card.image }}
blurhash={card.blurhash} blurhash={card.blurhash}
style={styles.left} style={styles.left}
imageStyle={styles.image}
/> />
)} )}
<View style={styles.right}> <View style={styles.right}>
@ -64,15 +65,13 @@ const styles = StyleSheet.create({
height: StyleConstants.Font.LineHeight.M * 5, height: StyleConstants.Font.LineHeight.M * 5,
marginTop: StyleConstants.Spacing.M, marginTop: StyleConstants.Spacing.M,
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
borderRadius: 6 borderRadius: 6,
overflow: 'hidden'
}, },
left: { left: {
width: StyleConstants.Font.LineHeight.M * 5, flexBasis: StyleConstants.Font.LineHeight.M * 5
height: StyleConstants.Font.LineHeight.M * 5
}, },
image: { image: {
width: '100%',
height: '100%',
borderTopLeftRadius: 6, borderTopLeftRadius: 6,
borderBottomLeftRadius: 6 borderBottomLeftRadius: 6
}, },

View File

@ -1,8 +1,11 @@
import analytics from '@components/analytics'
import Button from '@components/Button'
import { StackScreenProps } from '@react-navigation/stack' import { StackScreenProps } from '@react-navigation/stack'
import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice' import { getLocalAccount, getLocalUrl } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useMemo } from 'react' import React, { useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, View } from 'react-native' import { Dimensions, StyleSheet, View } from 'react-native'
import { import {
PanGestureHandler, PanGestureHandler,
@ -32,6 +35,8 @@ export type ScreenAccountProp = StackScreenProps<
const ScreenActions = React.memo( const ScreenActions = React.memo(
({ route: { params }, navigation }: ScreenAccountProp) => { ({ route: { params }, navigation }: ScreenAccountProp) => {
const { t } = useTranslation()
const localAccount = useSelector(getLocalAccount) const localAccount = useSelector(getLocalAccount)
let sameAccount = false let sameAccount = false
switch (params.type) { switch (params.type) {
@ -174,6 +179,15 @@ const ScreenActions = React.memo(
]} ]}
/> />
{actions} {actions}
<Button
type='text'
content={t('common:buttons.cancel')}
onPress={() => {
analytics('bottomsheet_cancel')
// dismiss()
}}
style={styles.button}
/>
</Animated.View> </Animated.View>
</PanGestureHandler> </PanGestureHandler>
</Animated.View> </Animated.View>

View File

@ -229,7 +229,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
style={[styles.base, {backgroundColor: 'red'}]} style={styles.base}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
> >
<SafeAreaView <SafeAreaView

View File

@ -2,7 +2,8 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { MutableRefObject, useContext } from 'react' import React, { MutableRefObject, useContext } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Dimensions, Image, StyleSheet, Text, View } from 'react-native' import { Dimensions, StyleSheet, Text, View } from 'react-native'
import FastImage from 'react-native-fast-image'
import { PanGestureHandler } from 'react-native-gesture-handler' import { PanGestureHandler } from 'react-native-gesture-handler'
import Animated, { import Animated, {
Extrapolate, Extrapolate,
@ -110,7 +111,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
return ( return (
<> <>
<View style={{ overflow: 'hidden', flex: 1, alignItems: 'center' }}> <View style={{ overflow: 'hidden', flex: 1, alignItems: 'center' }}>
<Image <FastImage
style={{ style={{
width: imageDimensionis.width, width: imageDimensionis.width,
height: imageDimensionis.height height: imageDimensionis.height

View File

@ -53,14 +53,6 @@ const ComposeRoot: React.FC = () => {
}) })
} }
}, [emojisData]) }, [emojisData])
useEffect(() => {
if (emojisData && emojisData.length) {
// Prefetch first batch of emojis for faster loading experience
emojisData.slice(0, 40).forEach(emoji => {
Image.prefetch(emoji.url)
})
}
}, [emojisData])
const listEmpty = useMemo(() => { const listEmpty = useMemo(() => {
if (isFetching) { if (isFetching) {

View File

@ -15,15 +15,9 @@ import React, {
useRef useRef
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native'
FlatList,
Image,
Pressable,
StyleSheet,
Text,
View
} from 'react-native'
import { Chase } from 'react-native-animated-spinkit' import { Chase } from 'react-native-animated-spinkit'
import FastImage from 'react-native-fast-image'
import ComposeContext from '../../utils/createContext' import ComposeContext from '../../utils/createContext'
import { ExtendedAttachment } from '../../utils/types' import { ExtendedAttachment } from '../../utils/types'
import addAttachment from './addAttachment' import addAttachment from './addAttachment'
@ -120,7 +114,7 @@ const ComposeAttachments: React.FC = () => {
key={index} key={index}
style={[styles.container, { width: calculateWidth(item) }]} style={[styles.container, { width: calculateWidth(item) }]}
> >
<Image <FastImage
style={styles.image} style={styles.image}
source={{ source={{
uri: item.local?.local_thumbnail || item.remote?.preview_url uri: item.local?.local_thumbnail || item.remote?.preview_url

View File

@ -1,18 +1,12 @@
import analytics from '@components/analytics'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useContext, useMemo } from 'react' import React, { useCallback, useContext, useMemo } from 'react'
import { import { Pressable, SectionList, StyleSheet, Text, View } from 'react-native'
Image, import FastImage from 'react-native-fast-image'
Pressable,
SectionList,
StyleSheet,
Text,
View
} from 'react-native'
import ComposeContext from '../../utils/createContext' import ComposeContext from '../../utils/createContext'
import updateText from '../../updateText' import updateText from '../../updateText'
import analytics from '@components/analytics'
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => { const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
@ -31,7 +25,7 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
haptics('Success') haptics('Success')
}, []) }, [])
const children = useMemo( const children = useMemo(
() => <Image source={{ uri: emoji.url }} style={styles.emoji} />, () => <FastImage source={{ uri: emoji.url }} style={styles.emoji} />,
[] []
) )
return ( return (

View File

@ -10,11 +10,10 @@ import { useTimelineQuery } from '@utils/queryHooks/timeline'
import { import {
getLocalAccount, getLocalAccount,
getLocalActiveIndex, getLocalActiveIndex,
getLocalNotification, getLocalNotification
localUpdateNotification
} from '@utils/slices/instancesSlice' } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useMemo } from 'react' import React, { useCallback, useMemo } from 'react'
import { Platform } from 'react-native' import { Platform } from 'react-native'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'

View File

@ -1,4 +1,4 @@
import Constants from 'expo-constants' import * as Updates from 'expo-updates'
import React from 'react' import React from 'react'
import { ScrollView } from 'react-native-gesture-handler' import { ScrollView } from 'react-native-gesture-handler'
import SettingsAnalytics from './Settings/Analytics' import SettingsAnalytics from './Settings/Analytics'
@ -15,7 +15,7 @@ const ScreenMeSettings: React.FC = () => {
{__DEV__ || {__DEV__ ||
['development'].some(channel => ['development'].some(channel =>
Constants.manifest.releaseChannel?.includes(channel) Updates.releaseChannel.includes(channel)
) ? ( ) ? (
<SettingsDev /> <SettingsDev />
) : null} ) : null}

View File

@ -6,6 +6,7 @@ import {
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import Constants from 'expo-constants' import Constants from 'expo-constants'
import * as Updates from 'expo-updates'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { StyleSheet, Text } from 'react-native' import { StyleSheet, Text } from 'react-native'
@ -31,7 +32,7 @@ const SettingsAnalytics: React.FC = () => {
<Text style={[styles.version, { color: theme.secondary }]}> <Text style={[styles.version, { color: theme.secondary }]}>
{t('content.version', { {t('content.version', {
version: Constants.manifest.version, version: Constants.manifest.version,
releaseChannel: Constants.manifest.releaseChannel || 'dev' releaseChannel: Updates.releaseChannel
})} })}
</Text> </Text>
</MenuContainer> </MenuContainer>

View File

@ -6,7 +6,7 @@ import { useSearchQuery } from '@utils/queryHooks/search'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice' import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import Constants from 'expo-constants' import * as Updates from 'expo-updates'
import * as Linking from 'expo-linking' import * as Linking from 'expo-linking'
import * as StoreReview from 'expo-store-review' import * as StoreReview from 'expo-store-review'
import * as WebBrowser from 'expo-web-browser' import * as WebBrowser from 'expo-web-browser'
@ -44,7 +44,7 @@ const SettingsTooot: React.FC = () => {
/> />
{__DEV__ || {__DEV__ ||
['production', 'development'].some(channel => ['production', 'development'].some(channel =>
Constants.manifest.releaseChannel?.includes(channel) Updates.releaseChannel?.includes(channel)
) ? ( ) ? (
<MenuRow <MenuRow
title={t('content.review.heading')} title={t('content.review.heading')}

View File

@ -7,7 +7,6 @@ import {
getLocalActiveIndex, getLocalActiveIndex,
getLocalInstances, getLocalInstances,
InstanceLocal, InstanceLocal,
InstancesState,
localUpdateActiveIndex localUpdateActiveIndex
} from '@utils/slices/instancesSlice' } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
@ -26,16 +25,11 @@ import { useQueryClient } from 'react-query'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
interface Props { interface Props {
index: NonNullable<InstancesState['local']['activeIndex']>
instance: InstanceLocal instance: InstanceLocal
disabled?: boolean disabled?: boolean
} }
const AccountButton: React.FC<Props> = ({ const AccountButton: React.FC<Props> = ({ instance, disabled = false }) => {
index,
instance,
disabled = false
}) => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const navigation = useNavigation() const navigation = useNavigation()
const dispatch = useDispatch() const dispatch = useDispatch()
@ -51,7 +45,7 @@ const AccountButton: React.FC<Props> = ({
onPress={() => { onPress={() => {
haptics('Light') haptics('Light')
analytics('switch_existing_press') analytics('switch_existing_press')
dispatch(localUpdateActiveIndex(index)) dispatch(localUpdateActiveIndex(instance))
queryClient.clear() queryClient.clear()
navigation.goBack() navigation.goBack()
}} }}
@ -86,14 +80,20 @@ const ScreenMeSwitchRoot: React.FC = () => {
`${b.uri}${b.account.acct}` `${b.uri}${b.account.acct}`
) )
) )
.map((instance, index) => ( .map((instance, index) => {
<AccountButton const localAccount = localInstances[localActiveIndex!]
key={index} return (
index={index} <AccountButton
instance={instance} key={index}
disabled={localActiveIndex === index} instance={instance}
/> disabled={
)) instance.url === localAccount.url &&
instance.token === localAccount.token &&
instance.account.id === localAccount.account.id
}
/>
)
})
: null} : null}
</View> </View>
</View> </View>

View File

@ -1,11 +1,7 @@
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext, useEffect } from 'react' import React, { useContext } from 'react'
import { Dimensions, Image } from 'react-native' import { Dimensions } from 'react-native'
import Animated, { import FastImage from 'react-native-fast-image'
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useSafeAreaInsets } from 'react-native-safe-area-context'
import AccountContext from './utils/createContext' import AccountContext from './utils/createContext'
@ -15,39 +11,18 @@ export interface Props {
} }
const AccountHeader: React.FC<Props> = ({ account, limitHeight = false }) => { const AccountHeader: React.FC<Props> = ({ account, limitHeight = false }) => {
const { accountState, accountDispatch } = useContext(AccountContext) const { accountState } = useContext(AccountContext)
const { theme } = useTheme() const { theme } = useTheme()
const topInset = useSafeAreaInsets().top const topInset = useSafeAreaInsets().top
const height = useSharedValue(
Dimensions.get('screen').width * accountState.headerRatio + topInset
)
const styleHeight = useAnimatedStyle(() => {
return {
height: withTiming(height.value)
}
})
useEffect(() => {
if (
account?.header &&
!account.header.includes('/headers/original/missing.png')
) {
Image.getSize(account.header, (width, height) => {
// if (!limitHeight) {
// accountDispatch({
// type: 'headerRatio',
// payload: height / width
// })
// }
})
}
}, [account])
return ( return (
<Animated.Image <FastImage
source={{ uri: account?.header }} source={{ uri: account?.header }}
style={[styleHeight, { backgroundColor: theme.disabled }]} style={{
height:
Dimensions.get('screen').width * accountState.headerRatio + topInset,
backgroundColor: theme.disabled
}}
/> />
) )
} }

View File

@ -88,7 +88,9 @@ const styles = StyleSheet.create({
} }
}) })
export default React.memo( export default React.memo(AccountInformation, (prev, next) => {
AccountInformation, let skipUpdate = true
(_, next) => next.account === undefined skipUpdate = prev.account?.id === next.account?.id
) skipUpdate = prev.account?.acct === next.account?.acct
return skipUpdate
})

View File

@ -3,7 +3,7 @@ import GracefullyImage from '@components/GracefullyImage'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack' import { StackNavigationProp } from '@react-navigation/stack'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import React, { useMemo } from 'react' import React from 'react'
import { Pressable, StyleSheet } from 'react-native' import { Pressable, StyleSheet } from 'react-native'
export interface Props { export interface Props {
@ -15,35 +15,33 @@ const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.TabLocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const dimension = useMemo(
() => ({
width: StyleConstants.Avatar.L,
height: StyleConstants.Avatar.L
}),
[]
)
return ( return (
<Pressable <Pressable
disabled={!myInfo} disabled={!myInfo}
onPress={() => { onPress={() => {
analytics('account_avatar_press') analytics('account_avatar_press')
myInfo && myInfo && account && navigation.push('Tab-Shared-Account', { account })
account &&
navigation.push('Tab-Shared-Account', { account })
}} }}
style={styles.base}
> >
<GracefullyImage <GracefullyImage
style={styles.base} key={account?.avatar}
style={styles.image}
uri={{ original: account?.avatar || '' }} uri={{ original: account?.avatar || '' }}
dimension={dimension}
/> />
</Pressable> </Pressable>
) )
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
base: { borderRadius: 8, overflow: 'hidden' } base: {
borderRadius: 8,
overflow: 'hidden',
width: StyleConstants.Avatar.L,
height: StyleConstants.Avatar.L
},
image: { flex: 1 }
}) })
export default AccountInformationAvatar export default AccountInformationAvatar

View File

@ -1,18 +1,17 @@
import Constants from 'expo-constants' import Constants from 'expo-constants'
import * as Updates from 'expo-updates'
import * as Sentry from 'sentry-expo' import * as Sentry from 'sentry-expo'
import log from './log' import log from './log'
const sentry = () => { const sentry = () => {
log('log', 'Sentry', 'initializing') log('log', 'Sentry', 'initializing')
Sentry.init({ Sentry.init({
environment: Constants.manifest.extra.sentryEnv,
dsn: Constants.manifest.extra.sentryDSN, dsn: Constants.manifest.extra.sentryDSN,
environment: Constants.manifest.extra.toootEnvironment,
enableInExpoDevelopment: false, enableInExpoDevelopment: false,
debug: debug:
__DEV__ || __DEV__ ||
['development'].some(channel => ['development'].some(channel => Updates.releaseChannel.includes(channel))
Constants.manifest.releaseChannel?.includes(channel)
)
}) })
} }

View File

@ -1,6 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { RootState } from '@root/store' import { RootState } from '@root/store'
import Constants from 'expo-constants' import * as Updates from 'expo-updates'
import * as StoreReview from 'expo-store-review' import * as StoreReview from 'expo-store-review'
export const supportedLngs = ['zh-Hans', 'en'] export const supportedLngs = ['zh-Hans', 'en']
@ -38,7 +38,7 @@ const contextsSlice = createSlice({
initialState: contextsInitialState as ContextsState, initialState: contextsInitialState as ContextsState,
reducers: { reducers: {
updateStoreReview: (state, action: PayloadAction<1>) => { updateStoreReview: (state, action: PayloadAction<1>) => {
if (Constants.manifest.releaseChannel?.includes('production')) { if (Updates.releaseChannel.includes('production')) {
state.storeReview.current = state.storeReview.current + action.payload state.storeReview.current = state.storeReview.current + action.payload
if (state.storeReview.current === state.storeReview.context) { if (state.storeReview.current === state.storeReview.context) {
StoreReview.isAvailableAsync().then(() => StoreReview.requestReview()) StoreReview.isAvailableAsync().then(() => StoreReview.requestReview())

View File

@ -175,15 +175,13 @@ const instancesSlice = createSlice({
name: 'instances', name: 'instances',
initialState: instancesInitialState, initialState: instancesInitialState,
reducers: { reducers: {
localUpdateActiveIndex: ( localUpdateActiveIndex: (state, action: PayloadAction<InstanceLocal>) => {
state, state.local.activeIndex = state.local.instances.findIndex(
action: PayloadAction<NonNullable<InstancesState['local']['activeIndex']>> instance =>
) => { instance.url === action.payload.url &&
if (action.payload < state.local.instances.length) { instance.token === action.payload.token &&
state.local.activeIndex = action.payload instance.account.id === action.payload.account.id
} else { )
throw new Error('Set index cannot be found')
}
}, },
localUpdateAccount: ( localUpdateAccount: (
state, state,