Move to expo and react-navigation (#288)

* WIP - adding expo

* WIP - adding expo 2

* Fix tsc

* Finish adding expo

* Disable the 'require cycle' warning

* Tweak plist

* Modify some dependency versions to make expo happy

* Fix icon fill

* Get Web compiling for expo

* 1.7

* Switch to react-navigation in expo2 (#287)

* WIP Switch to react-navigation

* WIP Switch to react-navigation 2

* WIP Switch to react-navigation 3

* Convert all screens to react navigation

* Update BottomBar for react navigation

* Update mobile menu to be react-native drawer

* Fixes to drawer and bottombar

* Factor out some helpers

* Replace the navigation model with react-navigation

* Restructure the shell folder and fix the header positioning

* Restore the error boundary

* Fix tsc

* Implement not-found page

* Remove react-native-gesture-handler (no longer used)

* Handle notifee card presses

* Handle all navigations from the state layer

* Fix drawer behaviors

* Fix two linking issues

* Switch to our react-native-progress fork to fix an svg rendering issue

* Get Web working with react-navigation

* Refactor routes and navigation for a bit more clarity

* Remove dead code

* Rework Web shell to left/right nav to make this easier

* Fix ViewHeader for desktop web

* Hide profileheader back btn on desktop web

* Move the compose button to the left nav

* Implement reply prompt in threads for desktop web

* Composer refactors

* Factor out all platform-specific text input behaviors from the composer

* Small fix

* Update the web build to use tiptap for the composer

* Tune up the mention autocomplete dropdown

* Simplify the default avatar and banner

* Fixes to link cards in web composer

* Fix dropdowns on web

* Tweak load latest on desktop

* Add web beta message and feedback link

* Fix up links in desktop web
This commit is contained in:
Paul Frazee 2023-03-13 16:01:43 -05:00 committed by GitHub
parent 503e03d91e
commit 56cf890deb
222 changed files with 8705 additions and 6338 deletions

77
src/lib/routes/helpers.ts Normal file
View file

@ -0,0 +1,77 @@
import {State, RouteParams} from './types'
export function getCurrentRoute(state: State) {
let node = state.routes[state.index || 0]
while (node.state?.routes && typeof node.state?.index === 'number') {
node = node.state?.routes[node.state?.index]
}
return node
}
export function isStateAtTabRoot(state: State | undefined) {
if (!state) {
// NOTE
// if state is not defined it's because init is occuring
// and therefore we can safely assume we're at root
// -prf
return true
}
const currentRoute = getCurrentRoute(state)
return (
isTab(currentRoute.name, 'Home') ||
isTab(currentRoute.name, 'Search') ||
isTab(currentRoute.name, 'Notifications')
)
}
export function isTab(current: string, route: string) {
// NOTE
// our tab routes can be variously referenced by 3 different names
// this helper deals with that weirdness
// -prf
return (
current === route ||
current === `${route}Tab` ||
current === `${route}Inner`
)
}
export enum TabState {
InsideAtRoot,
Inside,
Outside,
}
export function getTabState(state: State | undefined, tab: string): TabState {
if (!state) {
return TabState.Outside
}
const currentRoute = getCurrentRoute(state)
if (isTab(currentRoute.name, tab)) {
return TabState.InsideAtRoot
} else if (isTab(state.routes[state.index || 0].name, tab)) {
return TabState.Inside
}
return TabState.Outside
}
export function buildStateObject(
stack: string,
route: string,
params: RouteParams,
) {
if (stack === 'Flat') {
return {
routes: [{name: route, params}],
}
}
return {
routes: [
{
name: stack,
state: {
routes: [{name: route, params}],
},
},
],
}
}

55
src/lib/routes/router.ts Normal file
View file

@ -0,0 +1,55 @@
import {RouteParams, Route} from './types'
export class Router {
routes: [string, Route][] = []
constructor(description: Record<string, string>) {
for (const [screen, pattern] of Object.entries(description)) {
this.routes.push([screen, createRoute(pattern)])
}
}
matchName(name: string): Route | undefined {
for (const [screenName, route] of this.routes) {
if (screenName === name) {
return route
}
}
}
matchPath(path: string): [string, RouteParams] {
let name = 'NotFound'
let params: RouteParams = {}
for (const [screenName, route] of this.routes) {
const res = route.match(path)
if (res) {
name = screenName
params = res.params
break
}
}
return [name, params]
}
}
function createRoute(pattern: string): Route {
let matcherReInternal = pattern.replace(
/:([\w]+)/g,
(_m, name) => `(?<${name}>[^/]+)`,
)
const matcherRe = new RegExp(`^${matcherReInternal}([?]|$)`, 'i')
return {
match(path: string) {
const res = matcherRe.exec(path)
if (res) {
return {params: res.groups || {}}
}
return undefined
},
build(params: Record<string, string>) {
return pattern.replace(
/:([\w]+)/g,
(_m, name) => params[name] || 'undefined',
)
},
}
}

61
src/lib/routes/types.ts Normal file
View file

@ -0,0 +1,61 @@
import {NavigationState, PartialState} from '@react-navigation/native'
import type {NativeStackNavigationProp} from '@react-navigation/native-stack'
export type {NativeStackScreenProps} from '@react-navigation/native-stack'
export type CommonNavigatorParams = {
NotFound: undefined
Settings: undefined
Profile: {name: string}
ProfileFollowers: {name: string}
ProfileFollows: {name: string}
PostThread: {name: string; rkey: string}
PostUpvotedBy: {name: string; rkey: string}
PostRepostedBy: {name: string; rkey: string}
Debug: undefined
Log: undefined
}
export type HomeTabNavigatorParams = CommonNavigatorParams & {
Home: undefined
}
export type SearchTabNavigatorParams = CommonNavigatorParams & {
Search: undefined
}
export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
Notifications: undefined
}
export type FlatNavigatorParams = CommonNavigatorParams & {
Home: undefined
Search: undefined
Notifications: undefined
}
export type AllNavigatorParams = CommonNavigatorParams & {
HomeTab: undefined
Home: undefined
SearchTab: undefined
Search: undefined
NotificationsTab: undefined
Notifications: undefined
}
// NOTE
// this isn't strictly correct but it should be close enough
// a TS wizard might be able to get this 100%
// -prf
export type NavigationProp = NativeStackNavigationProp<AllNavigatorParams>
export type State =
| NavigationState
| Omit<PartialState<NavigationState>, 'stale'>
export type RouteParams = Record<string, string>
export type MatchResult = {params: RouteParams}
export type Route = {
match: (path: string) => MatchResult | undefined
build: (params: RouteParams) => string
}