Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Android & visual fixes: color themes, repost icon, navigation, back handler, etc (#519)

* Switch android to use slide left/right animations on navigation

* Bump the repost icon down by a pixel

* Tune theme colors for contrast and darker bg on darkmode

* Move back handler to a point in the init flow that leads to more consistent capture of events

* Fix image share flow on android

* Fix lint

* Add todo about sharing not available

* Drop the android slide animation because it's too slow

* Fix 'flashes of white' in dark mode android

authored by

Paul Frazee and committed by
GitHub
da8af38d 9d8600c2

+54 -36
+1
package.json
··· 68 68 "expo-image-picker": "~14.1.1", 69 69 "expo-localization": "~14.1.1", 70 70 "expo-media-library": "~15.2.3", 71 + "expo-sharing": "~11.2.2", 71 72 "expo-splash-screen": "~0.18.1", 72 73 "expo-status-bar": "~1.4.4", 73 74 "expo-system-ui": "~2.2.1",
-2
src/App.native.tsx
··· 13 13 import {Shell} from './view/shell' 14 14 import * as notifee from 'lib/notifee' 15 15 import * as analytics from 'lib/analytics' 16 - import * as backHandler from 'lib/routes/back-handler' 17 16 import * as Toast from './view/com/util/Toast' 18 17 import {handleLink} from './Navigation' 19 18 ··· 29 28 setRootStore(store) 30 29 analytics.init(store) 31 30 notifee.init(store) 32 - backHandler.init(store) 33 31 SplashScreen.hideAsync() 34 32 Linking.getInitialURL().then((url: string | null) => { 35 33 if (url) {
+5 -2
src/Navigation.tsx
··· 6 6 createNavigationContainerRef, 7 7 CommonActions, 8 8 StackActions, 9 + DefaultTheme, 10 + DarkTheme, 9 11 } from '@react-navigation/native' 10 12 import {createNativeStackNavigator} from '@react-navigation/native-stack' 11 13 import {createBottomTabNavigator} from '@react-navigation/bottom-tabs' ··· 256 258 } 257 259 258 260 function RoutesContainer({children}: React.PropsWithChildren<{}>) { 261 + const theme = useColorSchemeStyle(DefaultTheme, DarkTheme) 259 262 return ( 260 - <NavigationContainer ref={navigationRef} linking={LINKING}> 263 + <NavigationContainer ref={navigationRef} linking={LINKING} theme={theme}> 261 264 {children} 262 265 </NavigationContainer> 263 266 ) ··· 334 337 backgroundColor: colors.black, 335 338 }, 336 339 bgLight: { 337 - backgroundColor: colors.gray1, 340 + backgroundColor: colors.white, 338 341 }, 339 342 }) 340 343
+1 -1
src/lib/icons.tsx
··· 431 431 strokeWidth={strokeWidth} 432 432 strokeLinejoin="round" 433 433 fill="none" 434 - d="M 14.437 17.081 L 5.475 17.095 C 4.7 17.095 4.072 16.467 4.072 15.692 L 4.082 5.65 L 1.22 9.854 M 4.082 5.65 L 7.006 9.854 M 9.859 5.65 L 18.625 5.654 C 19.4 5.654 20.028 6.282 20.028 7.057 L 20.031 17.081 L 17.167 12.646 M 20.031 17.081 L 22.866 12.646" 434 + d="M 14.437 18.081 L 5.475 18.095 C 4.7 18.095 4.072 17.467 4.072 16.692 L 4.082 6.65 L 1.22 10.854 M 4.082 6.65 L 7.006 10.854 M 9.859 6.65 L 18.625 6.654 C 19.4 6.654 20.028 7.282 20.028 8.057 L 20.031 18.081 L 17.167 13.646 M 20.031 18.081 L 22.866 13.646" 435 435 /> 436 436 </Svg> 437 437 )
+12 -13
src/lib/media/manip.ts
··· 1 1 import RNFetchBlob from 'rn-fetch-blob' 2 2 import ImageResizer from '@bam.tech/react-native-image-resizer' 3 - import {Image as RNImage, Share} from 'react-native' 3 + import {Image as RNImage} from 'react-native' 4 4 import {Image} from 'react-native-image-crop-picker' 5 5 import RNFS from 'react-native-fs' 6 6 import uuid from 'react-native-uuid' 7 - import * as Toast from 'view/com/util/Toast' 7 + import * as Sharing from 'expo-sharing' 8 8 import {Dimensions} from './types' 9 9 import {POST_IMG_MAX} from 'lib/constants' 10 10 import {isAndroid} from 'platform/detection' ··· 120 120 } 121 121 122 122 export async function saveImageModal({uri}: {uri: string}) { 123 + if (!(await Sharing.isAvailableAsync())) { 124 + // TODO might need to give an error to the user in this case -prf 125 + return 126 + } 123 127 const downloadResponse = await RNFetchBlob.config({ 124 128 fileCache: true, 125 129 }).fetch('GET', uri) 126 130 127 - const imagePath = downloadResponse.path() 128 - const base64Data = await downloadResponse.readFile('base64') 129 - const result = await Share.share({ 130 - url: 'data:image/png;base64,' + base64Data, 131 + let imagePath = downloadResponse.path() 132 + await Sharing.shareAsync(normalizePath(imagePath, true), { 133 + mimeType: 'image/png', 134 + UTI: 'public.png', 131 135 }) 132 - if (result.action === Share.sharedAction) { 133 - Toast.show('Image saved to gallery') 134 - } else if (result.action === Share.dismissedAction) { 135 - // dismissed 136 - } 137 136 RNFS.unlink(imagePath) 138 137 } 139 138 ··· 201 200 return normalizePath(destinationPath) 202 201 } 203 202 204 - function normalizePath(str: string): string { 205 - if (isAndroid) { 203 + function normalizePath(str: string, allPlatforms = false): string { 204 + if (isAndroid || allPlatforms) { 206 205 if (!str.startsWith('file://')) { 207 206 return `file://${str}` 208 207 }
+3 -6
src/lib/routes/back-handler.ts
··· 1 1 import {BackHandler} from 'react-native' 2 2 import {RootStoreModel} from 'state/index' 3 3 4 - export function onBack(cb: () => boolean): () => void { 5 - const subscription = BackHandler.addEventListener('hardwareBackPress', cb) 6 - return () => subscription.remove() 7 - } 8 - 9 4 export function init(store: RootStoreModel) { 10 - onBack(() => store.shell.closeAnyActiveElement()) 5 + BackHandler.addEventListener('hardwareBackPress', () => { 6 + return store.shell.closeAnyActiveElement() 7 + }) 11 8 }
+3 -3
src/lib/themes.ts
··· 291 291 palette: { 292 292 ...defaultTheme.palette, 293 293 default: { 294 - background: colors.gray8, 295 - backgroundLight: colors.gray6, 294 + background: colors.black, 295 + backgroundLight: colors.gray7, 296 296 text: colors.white, 297 297 textLight: colors.gray3, 298 298 textInverted: colors.black, ··· 307 307 replyLineDot: colors.gray6, 308 308 unreadNotifBg: colors.blue7, 309 309 unreadNotifBorder: colors.blue6, 310 - postCtrl: '#61657A', 310 + postCtrl: '#707489', 311 311 brandText: '#0085ff', 312 312 emptyStateIcon: colors.gray4, 313 313 },
+3 -3
src/view/com/composer/Composer.tsx
··· 33 33 import {usePalette} from 'lib/hooks/usePalette' 34 34 import QuoteEmbed from '../util/post-embeds/QuoteEmbed' 35 35 import {useExternalLinkFetch} from './useExternalLinkFetch' 36 - import {isDesktopWeb} from 'platform/detection' 36 + import {isDesktopWeb, isAndroid} from 'platform/detection' 37 37 import {GalleryModel} from 'state/models/media/gallery' 38 38 import {Gallery} from './photos/Gallery' 39 39 ··· 195 195 196 196 const canSelectImages = gallery.size <= 4 197 197 const viewStyles = { 198 - paddingBottom: Platform.OS === 'android' ? insets.bottom : 0, 199 - paddingTop: Platform.OS === 'android' ? insets.top : 15, 198 + paddingBottom: isAndroid ? insets.bottom : 0, 199 + paddingTop: isAndroid ? insets.top : isDesktopWeb ? 0 : 15, 200 200 } 201 201 202 202 return (
+2
src/view/com/modals/Modal.web.tsx
··· 97 97 styles.container, 98 98 isMobileWeb && styles.containerMobile, 99 99 pal.view, 100 + pal.border, 100 101 ]}> 101 102 {element} 102 103 </View> ··· 124 125 paddingVertical: 20, 125 126 paddingHorizontal: 24, 126 127 borderRadius: 8, 128 + borderWidth: 1, 127 129 }, 128 130 containerMobile: { 129 131 borderRadius: 0,
+5 -1
src/view/com/util/forms/DropdownButton.tsx
··· 291 291 const theme = useTheme() 292 292 const dropDownBackgroundColor = 293 293 theme.colorScheme === 'dark' ? pal.btn : pal.view 294 + const separatorColor = 295 + theme.colorScheme === 'dark' ? pal.borderDark : pal.border 294 296 295 297 return ( 296 298 <> ··· 322 324 </TouchableOpacity> 323 325 ) 324 326 } else if (isSep(item)) { 325 - return <View key={index} style={[styles.separator, pal.border]} /> 327 + return ( 328 + <View key={index} style={[styles.separator, separatorColor]} /> 329 + ) 326 330 } 327 331 return null 328 332 })}
+7 -3
src/view/screens/Settings.tsx
··· 35 35 import {useAnalytics} from 'lib/analytics' 36 36 import {NavigationProp} from 'lib/routes/types' 37 37 import {pluralize} from 'lib/strings/helpers' 38 + import {isDesktopWeb} from 'platform/detection' 38 39 39 40 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> 40 41 export const SettingsScreen = withAuthRequired( ··· 139 140 }, [store]) 140 141 141 142 return ( 142 - <View style={[s.hContentRegion]} testID="settingsScreen"> 143 - <ViewHeader title="Settings" showOnDesktop /> 144 - <ScrollView style={s.hContentRegion} scrollIndicatorInsets={{right: 1}}> 143 + <View style={s.hContentRegion} testID="settingsScreen"> 144 + <ViewHeader title="Settings" /> 145 + <ScrollView 146 + style={s.hContentRegion} 147 + contentContainerStyle={!isDesktopWeb && pal.viewLight} 148 + scrollIndicatorInsets={{right: 1}}> 145 149 <View style={styles.spacer20} /> 146 150 <View style={[s.flexRow, styles.heading]}> 147 151 <Text type="xl-bold" style={pal.text}>
+2 -1
src/view/shell/Composer.web.tsx
··· 32 32 33 33 return ( 34 34 <View style={styles.mask}> 35 - <View style={[styles.container, pal.view]}> 35 + <View style={[styles.container, pal.view, pal.border]}> 36 36 <ComposePost 37 37 replyTo={replyTo} 38 38 quote={quote} ··· 63 63 paddingHorizontal: 2, 64 64 borderRadius: isMobileWeb ? 0 : 8, 65 65 marginBottom: '10vh', 66 + borderWidth: 1, 66 67 }, 67 68 })
+5 -1
src/view/shell/index.tsx
··· 13 13 import {Composer} from './Composer' 14 14 import {useTheme} from 'lib/ThemeContext' 15 15 import {usePalette} from 'lib/hooks/usePalette' 16 + import * as backHandler from 'lib/routes/back-handler' 16 17 import {RoutesContainer, TabsNavigator} from '../../Navigation' 17 18 import {isStateAtTabRoot} from 'lib/routes/helpers' 18 19 ··· 34 35 [store], 35 36 ) 36 37 const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) 38 + React.useEffect(() => { 39 + backHandler.init(store) 40 + }, [store]) 37 41 38 42 return ( 39 43 <> ··· 69 73 }) 70 74 71 75 export const Shell: React.FC = observer(() => { 72 - const theme = useTheme() 73 76 const pal = usePalette('default') 77 + const theme = useTheme() 74 78 return ( 75 79 <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}> 76 80 <StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
+5
yarn.lock
··· 8427 8427 commander "2.20.0" 8428 8428 update-check "1.5.3" 8429 8429 8430 + expo-sharing@~11.2.2: 8431 + version "11.2.2" 8432 + resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-11.2.2.tgz#7d9e387f1a902e6dd6838c22d9599dae9e7432cf" 8433 + integrity sha512-4Lhm1eS/CFIzX+JPuxMUTWBt9rv/WdvJvpQ9y+71bL/9w9dhvsdt9tv0SsNZATz4hk0tbrYD8ZEUsgiHiT1KkQ== 8434 + 8430 8435 expo-splash-screen@~0.18.1: 8431 8436 version "0.18.1" 8432 8437 resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.18.1.tgz#e090b045a7f8c5d9597b7a96910caa4eae1fcf3b"