diff --git a/ios/app.xcodeproj/project.pbxproj b/ios/app.xcodeproj/project.pbxproj
index 7d23faf6f6..e2127a5eb8 100644
--- a/ios/app.xcodeproj/project.pbxproj
+++ b/ios/app.xcodeproj/project.pbxproj
@@ -459,6 +459,7 @@
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = app/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -487,6 +488,7 @@
DEVELOPMENT_TEAM = B3LX46C5HS;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = app/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/ios/app/Info.plist b/ios/app/Info.plist
index 5b5202592c..bccc5afa0c 100644
--- a/ios/app/Info.plist
+++ b/ios/app/Info.plist
@@ -49,11 +49,11 @@
NSCameraUsageDescription
-
+ Used to take pictures and videos when composing posts, choosing avatars, and so on.
NSLocationWhenInUseUsageDescription
NSPhotoLibraryUsageDescription
-
+ Used to upload pictures and videos when composing posts, choosing avatars, and so on.
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
diff --git a/src/state/models/me.ts b/src/state/models/me.ts
index fde387ebe4..25b148c754 100644
--- a/src/state/models/me.ts
+++ b/src/state/models/me.ts
@@ -5,10 +5,10 @@ import {NotificationsViewModel} from './notifications-view'
import {isObj, hasProp} from '../lib/type-guards'
export class MeModel {
- did?: string
- handle?: string
- displayName?: string
- description?: string
+ did: string = ''
+ handle: string = ''
+ displayName: string = ''
+ description: string = ''
notificationCount: number = 0
memberships?: MembershipsViewModel
notifications: NotificationsViewModel
@@ -23,10 +23,10 @@ export class MeModel {
}
clear() {
- this.did = undefined
- this.handle = undefined
- this.displayName = undefined
- this.description = undefined
+ this.did = ''
+ this.handle = ''
+ this.displayName = ''
+ this.description = ''
this.notificationCount = 0
this.memberships = undefined
}
@@ -58,8 +58,8 @@ export class MeModel {
if (did && handle) {
this.did = did
this.handle = handle
- this.displayName = displayName
- this.description = description
+ this.displayName = displayName || ''
+ this.description = description || ''
}
}
}
@@ -74,8 +74,8 @@ export class MeModel {
})
runInAction(() => {
if (profile?.data) {
- this.displayName = profile.data.displayName
- this.description = profile.data.description
+ this.displayName = profile.data.displayName || ''
+ this.description = profile.data.description || ''
} else {
this.displayName = ''
this.description = ''
diff --git a/src/state/models/navigation.ts b/src/state/models/navigation.ts
index 6eb13a62ae..93aab9d4f1 100644
--- a/src/state/models/navigation.ts
+++ b/src/state/models/navigation.ts
@@ -17,8 +17,11 @@ export type HistoryPtr = [number, number]
export class NavigationTabModel {
id = genId()
- history: HistoryItem[] = [{url: '/', ts: Date.now(), id: genId()}]
- index = 0
+ history: HistoryItem[] = [
+ {url: '/menu', ts: Date.now(), id: genId()},
+ {url: '/', ts: Date.now(), id: genId()},
+ ]
+ index = 1
isNewTab = false
constructor() {
@@ -107,9 +110,15 @@ export class NavigationTabModel {
}
}
- goBackToZero() {
- if (this.canGoBack) {
- this.index = 0
+ resetTo(path: string) {
+ if (this.index >= 1 && this.history[1]?.url === path) {
+ // fall back in history to target
+ if (this.index > 1) {
+ this.index = 1
+ }
+ } else {
+ this.history = [this.history[0], {url: path, ts: Date.now(), id: genId()}]
+ this.index = 1
}
}
diff --git a/src/state/models/session.ts b/src/state/models/session.ts
index 069e3db32b..1537d13166 100644
--- a/src/state/models/session.ts
+++ b/src/state/models/session.ts
@@ -138,7 +138,10 @@ export class SessionModel {
}
async connect(): Promise {
- this._connectPromise ??= this._connect()
+ if (this._connectPromise) {
+ return this._connectPromise
+ }
+ this._connectPromise = this._connect()
await this._connectPromise
this._connectPromise = undefined
}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 93cf4be375..a90990b5ac 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -377,7 +377,6 @@ const styles = StyleSheet.create({
postTextLarge: {
fontSize: 24,
lineHeight: 32,
- fontWeight: '300',
},
postTextLargeContainer: {
paddingLeft: 4,
diff --git a/src/view/lib/icons.tsx b/src/view/lib/icons.tsx
index 7e3313597c..31869810d0 100644
--- a/src/view/lib/icons.tsx
+++ b/src/view/lib/icons.tsx
@@ -166,6 +166,38 @@ export function BellIconSolid({
)
}
+export function CogIcon({
+ style,
+ size,
+ strokeWidth = 1.5,
+}: {
+ style?: StyleProp
+ size?: string | number
+ strokeWidth: number
+}) {
+ return (
+
+ )
+}
+
// Copyright (c) 2020 Refactoring UI Inc.
// https://github.com/tailwindlabs/heroicons/blob/master/LICENSE
export function UserGroupIcon({
diff --git a/src/view/routes.ts b/src/view/routes.ts
index 272a1b096d..e662e2cca7 100644
--- a/src/view/routes.ts
+++ b/src/view/routes.ts
@@ -1,6 +1,7 @@
import React, {MutableRefObject} from 'react'
import {FlatList} from 'react-native'
import {IconProp} from '@fortawesome/fontawesome-svg-core'
+import {Menu} from './screens/Menu'
import {Home} from './screens/Home'
import {Contacts} from './screens/Contacts'
import {Search} from './screens/Search'
@@ -33,6 +34,7 @@ export type MatchResult = {
const r = (pattern: string) => new RegExp('^' + pattern + '([?]|$)', 'i')
export const routes: Route[] = [
+ [Menu, 'Menu', 'bars', r('/menu')],
[Home, 'Home', 'house', r('/')],
[Contacts, 'Contacts', ['far', 'circle-user'], r('/contacts')],
[Search, 'Search', 'magnifying-glass', r('/search')],
diff --git a/src/view/screens/Menu.tsx b/src/view/screens/Menu.tsx
new file mode 100644
index 0000000000..d226ea02b0
--- /dev/null
+++ b/src/view/screens/Menu.tsx
@@ -0,0 +1,246 @@
+import React, {useEffect} from 'react'
+import {
+ StyleProp,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+ ViewStyle,
+} from 'react-native'
+import {colors} from '../lib/styles'
+import {ScreenParams} from '../routes'
+import {useStores} from '../../state'
+import {
+ HomeIcon,
+ UserGroupIcon,
+ BellIcon,
+ CogIcon,
+ MagnifyingGlassIcon,
+} from '../lib/icons'
+import {UserAvatar} from '../com/util/UserAvatar'
+import {ViewHeader} from '../com/util/ViewHeader'
+import {CreateSceneModel} from '../../state/models/shell-ui'
+
+export const Menu = ({navIdx, visible}: ScreenParams) => {
+ const store = useStores()
+
+ useEffect(() => {
+ if (visible) {
+ store.nav.setTitle(navIdx, 'Menu')
+ // trigger a refresh in case memberships have changed recently
+ store.me.refreshMemberships()
+ }
+ }, [store, visible])
+
+ // events
+ // =
+
+ const onNavigate = (url: string) => {
+ store.nav.navigate(url)
+ }
+ const onPressCreateScene = () => {
+ store.shell.openModal(new CreateSceneModel())
+ }
+
+ // rendering
+ // =
+
+ const MenuItem = ({
+ icon,
+ label,
+ count,
+ url,
+ bold,
+ onPress,
+ }: {
+ icon: JSX.Element
+ label: string
+ count?: number
+ url?: string
+ bold?: boolean
+ onPress?: () => void
+ }) => (
+ onNavigate(url || '/')}>
+
+ {icon}
+ {count ? (
+
+ {count}
+
+ ) : undefined}
+
+
+ {label}
+
+
+ )
+
+ /*TODO */
+ return (
+
+
+ onNavigate('/search')}>
+ }
+ size={21}
+ />
+ Search
+
+
+
+ }
+ label={store.me.displayName || store.me.handle}
+ bold
+ url={`/profile/${store.me.handle}`}
+ />
+
+
+ Scenes
+ }
+ size="24"
+ />
+ }
+ label="Create a scene"
+ onPress={onPressCreateScene}
+ />
+ {store.me.memberships
+ ? store.me.memberships.memberships.map((membership, i) => (
+
+ }
+ label={membership.displayName || membership.handle}
+ url={`/profile/${membership.handle}`}
+ />
+ ))
+ : undefined}
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ view: {
+ flex: 1,
+ backgroundColor: colors.white,
+ },
+ section: {
+ paddingHorizontal: 10,
+ paddingTop: 10,
+ paddingBottom: 10,
+ borderBottomWidth: 1,
+ borderBottomColor: colors.gray1,
+ },
+ heading: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ paddingVertical: 8,
+ paddingHorizontal: 4,
+ },
+
+ searchBtn: {
+ flexDirection: 'row',
+ backgroundColor: colors.gray1,
+ borderRadius: 8,
+ margin: 10,
+ marginBottom: 0,
+ paddingVertical: 10,
+ paddingHorizontal: 12,
+ },
+ searchBtnLabel: {
+ marginLeft: 8,
+ fontSize: 18,
+ color: colors.gray6,
+ },
+
+ menuItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: 8,
+ paddingHorizontal: 2,
+ },
+ menuItemIconWrapper: {
+ width: 30,
+ height: 30,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginRight: 10,
+ },
+ menuItemLabel: {
+ fontSize: 17,
+ color: colors.gray7,
+ },
+ menuItemLabelBold: {
+ fontWeight: 'bold',
+ },
+ menuItemCount: {
+ position: 'absolute',
+ right: -6,
+ top: -2,
+ backgroundColor: colors.red3,
+ paddingHorizontal: 4,
+ paddingBottom: 1,
+ borderRadius: 6,
+ },
+ menuItemCountLabel: {
+ fontSize: 12,
+ fontWeight: 'bold',
+ color: colors.white,
+ },
+})
diff --git a/src/view/shell/mobile/MainMenu.tsx b/src/view/shell/mobile/MainMenu.tsx
deleted file mode 100644
index 8a72646129..0000000000
--- a/src/view/shell/mobile/MainMenu.tsx
+++ /dev/null
@@ -1,354 +0,0 @@
-import React, {useEffect} from 'react'
-import {observer} from 'mobx-react-lite'
-import {
- StyleSheet,
- SafeAreaView,
- Text,
- TouchableOpacity,
- TouchableWithoutFeedback,
- View,
-} from 'react-native'
-import Animated, {
- useSharedValue,
- useAnimatedStyle,
- withTiming,
- interpolate,
-} from 'react-native-reanimated'
-import {IconProp} from '@fortawesome/fontawesome-svg-core'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import _chunk from 'lodash.chunk'
-import {HomeIcon, UserGroupIcon, BellIcon} from '../../lib/icons'
-import {UserAvatar} from '../../com/util/UserAvatar'
-import {useStores} from '../../../state'
-import {CreateSceneModel} from '../../../state/models/shell-ui'
-import {s, colors} from '../../lib/styles'
-
-export const MainMenu = observer(
- ({
- active,
- insetBottom,
- onClose,
- }: {
- active: boolean
- insetBottom: number
- onClose: () => void
- }) => {
- const store = useStores()
- const initInterp = useSharedValue(0)
-
- useEffect(() => {
- if (active) {
- // trigger a refresh in case memberships have changed recently
- store.me.refreshMemberships()
- }
- }, [active])
- useEffect(() => {
- if (active) {
- initInterp.value = withTiming(1, {duration: 150})
- } else {
- initInterp.value = 0
- }
- }, [initInterp, active])
- const wrapperAnimStyle = useAnimatedStyle(() => ({
- opacity: interpolate(initInterp.value, [0, 1.0], [0, 1.0]),
- }))
- const menuItemsAnimStyle = useAnimatedStyle(() => ({
- top: interpolate(initInterp.value, [0, 1.0], [15, 0]),
- }))
-
- // events
- // =
-
- const onNavigate = (url: string) => {
- store.nav.navigate(url)
- onClose()
- }
- const onPressCreateScene = () => {
- store.shell.openModal(new CreateSceneModel())
- onClose()
- }
-
- // rendering
- // =
-
- const MenuItemBlank = () => (
-
- )
-
- const MenuItem = ({
- icon,
- label,
- count,
- url,
- onPress,
- }: {
- icon: IconProp
- label: string
- count?: number
- url?: string
- onPress?: () => void
- }) => (
- onNavigate(url || '/')}>
-
- {icon === 'home' ? (
-
- ) : icon === 'user-group' ? (
-
- ) : icon === 'bell' ? (
-
- ) : (
-
- )}
-
- {count ? (
-
- {count}
-
- ) : undefined}
-
- {label}
-
-
- )
- const MenuItemActor = ({
- label,
- url,
- count,
- }: {
- label: string
- url: string
- count?: number
- }) => (
- onNavigate(url)}>
-
-
-
- {count ? (
-
- {count}
-
- ) : undefined}
-
- {label}
-
-
- )
-
- if (!active) {
- return
- }
-
- const MenuItems = ({
- children,
- }: {
- children: (JSX.Element | JSX.Element[])[]
- }) => {
- const groups = _chunk(children.flat(), 4)
- const lastGroup = groups.at(-1)
- while (lastGroup && lastGroup.length < 4) {
- lastGroup.push()
- }
- return (
- <>
- {groups.map((group, i) => (
-
- {group.map((el, j) => (
- {el}
- ))}
-
- ))}
- >
- )
- }
-
- /*TODO */
- return (
- <>
-
-
-
-
-
-
- onNavigate(`/profile/${store.me.handle || ''}`)}>
-
-
-
-
- {store.me.displayName || store.me.handle || 'My profile'}
-
-
-
- onNavigate(`/settings`)}>
-
-
-
-
-
-
-
-
-
- Scenes
-
-
- {store.me.memberships ? (
- store.me.memberships.memberships.map((membership, i) => (
-
- ))
- ) : (
-
- )}
-
-
-
-
- >
- )
- },
-)
-
-const styles = StyleSheet.create({
- bg: {
- position: 'absolute',
- top: 0,
- right: 0,
- bottom: 0,
- left: 0,
- // backgroundColor: '#000',
- opacity: 0,
- },
- wrapper: {
- position: 'absolute',
- top: 0,
- width: '100%',
- backgroundColor: '#fff',
- },
-
- topSection: {
- flexDirection: 'row',
- alignItems: 'center',
- height: 40,
- paddingHorizontal: 10,
- marginTop: 12,
- marginBottom: 20,
- },
- section: {
- paddingHorizontal: 10,
- },
- heading: {
- fontSize: 21,
- fontWeight: 'bold',
- paddingHorizontal: 10,
- paddingTop: 6,
- paddingBottom: 12,
- },
-
- profile: {
- paddingVertical: 10,
- paddingHorizontal: 10,
- flexDirection: 'row',
- alignItems: 'center',
- },
- profileImage: {
- marginRight: 8,
- },
- profileText: {
- fontSize: 17,
- fontWeight: 'bold',
- },
-
- settings: {},
- settingsIcon: {
- color: colors.gray5,
- marginRight: 10,
- },
-
- menuItemsAnimContainer: {
- position: 'relative',
- },
- menuItems: {
- flexDirection: 'row',
- marginBottom: 20,
- },
- menuItem: {
- flex: 1,
- alignItems: 'center',
- },
- menuItemMargin: {
- marginRight: 10,
- },
- menuItemIconWrapper: {
- borderRadius: 6,
- width: 60,
- height: 60,
- justifyContent: 'center',
- alignItems: 'center',
- marginBottom: 5,
- backgroundColor: colors.gray1,
- },
- menuItemIcon: {
- color: colors.gray5,
- },
- menuItemLabel: {
- fontSize: 13,
- textAlign: 'center',
- },
- menuItemCount: {
- position: 'absolute',
- left: 48,
- top: 10,
- backgroundColor: colors.red3,
- paddingHorizontal: 4,
- paddingBottom: 1,
- borderRadius: 6,
- },
- menuItemCountLabel: {
- fontSize: 12,
- fontWeight: 'bold',
- color: colors.white,
- },
-})
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index e3e30decc7..6bb111877a 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -33,7 +33,6 @@ import {match, MatchResult} from '../../routes'
import {Login} from '../../screens/Login'
import {Onboard} from '../../screens/Onboard'
import {Modal} from '../../com/modals/Modal'
-import {MainMenu} from './MainMenu'
import {TabsSelector} from './TabsSelector'
import {Composer} from './Composer'
import {s, colors} from '../../lib/styles'
@@ -118,7 +117,6 @@ const Btn = ({
export const MobileShell: React.FC = observer(() => {
const store = useStores()
- const [isMainMenuActive, setMainMenuActive] = useState(false)
const [isTabsSelectorActive, setTabsSelectorActive] = useState(false)
const scrollElRef = useRef()
const winDim = useWindowDimensions()
@@ -134,16 +132,10 @@ export const MobileShell: React.FC = observer(() => {
if (store.nav.tab.current.url === '/') {
scrollElRef.current?.scrollToOffset({offset: 0})
} else {
- if (store.nav.tab.canGoBack) {
- // sanity check
- store.nav.tab.goBackToZero()
- } else {
- store.nav.navigate('/')
- }
+ store.nav.tab.resetTo('/')
}
}
- const onPressMenu = () => setMainMenuActive(true)
- const onPressNotifications = () => store.nav.navigate('/notifications')
+ const onPressNotifications = () => store.nav.tab.resetTo('/notifications')
const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive)
const doNewTab = (url: string) => () => store.nav.newTab(url)
@@ -337,16 +329,7 @@ export const MobileShell: React.FC = observer(() => {
onLongPress={TABS_ENABLED ? doNewTab('/notifications') : undefined}
notificationCount={store.me.notificationCount}
/>
-
- setMainMenuActive(false)}
- />