Merge remote-tracking branch 'origin/main' into eric/app-864-integrate-post-tags-into-app

* origin/main: (40 commits)
  1.52
  README: tweaks to high-level context (#1625)
  Fix stuck lightbox header after double tap (#1627)
  Fix: add padding to the spinner bottom while loading threads (#1626)
  Rewrite Android lightbox (#1624)
  Dont trim before posting (close #1621) (#1622)
  Only listen to back button on android (#1623)
  Improve typeahead search with inclusion of followed users (temporary solution) (#1612)
  Slightly smaller highlighted post text (#1608)
  Pull upstream bugfixes to bottom-sheet (#1606)
  Fix animations and gestures getting reset on state updates in the lightbox (#1618)
  Remove unused lightbox options (#1616)
  Profile UI tweaks (#1607)
  Fix invite codes flash on desktop, use loading placeholder (#1591)
  Update to react-native@0.72.5 (#1599)
  Fixed a typo on the onboarding recommended screen (#1604)
  Onboarding & feed fixes (#1602)
  Improve time to content in the search page (#1603)
  Fix a potential reference error in bottombarweb (#1600)
  Fix: only use scroll-positioning control on thread when looking at replies (#1587)
  ...
This commit is contained in:
Eric Bailey
2023-10-09 11:02:43 -05:00
108 changed files with 3705 additions and 2167 deletions
+1 -1
View File
@@ -95,7 +95,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
| undefined
let reply
let rt = new RichText(
{text: opts.rawText.trim()},
{text: opts.rawText.trimEnd()},
{
cleanNewlines: true,
},
+3 -10
View File
@@ -79,6 +79,7 @@ export async function DEFAULT_FEEDS(
serviceUrl: string,
resolveHandle: (name: string) => Promise<string>,
) {
// TODO: remove this when the test suite no longer relies on it
if (IS_LOCAL_DEV(serviceUrl)) {
// local dev
const aliceDid = await resolveHandle('alice.test')
@@ -106,16 +107,8 @@ export async function DEFAULT_FEEDS(
} else {
// production
return {
pinned: [
PROD_DEFAULT_FEED('whats-hot'),
PROD_DEFAULT_FEED('with-friends'),
],
saved: [
PROD_DEFAULT_FEED('bsky-team'),
PROD_DEFAULT_FEED('with-friends'),
PROD_DEFAULT_FEED('whats-hot'),
PROD_DEFAULT_FEED('hot-classic'),
],
pinned: [PROD_DEFAULT_FEED('whats-hot')],
saved: [PROD_DEFAULT_FEED('whats-hot')],
}
}
}
+41
View File
@@ -0,0 +1,41 @@
import {useCallback, useState} from 'react'
import {useStores} from 'state/index'
import {useAnalytics} from 'lib/analytics/analytics'
import {StackActions, useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {AccountData} from 'state/models/session'
import {reset as resetNavigation} from '../../Navigation'
import * as Toast from 'view/com/util/Toast'
export function useAccountSwitcher(): [
boolean,
(v: boolean) => void,
(acct: AccountData) => Promise<void>,
] {
const {track} = useAnalytics()
const store = useStores()
const [isSwitching, setIsSwitching] = useState(false)
const navigation = useNavigation<NavigationProp>()
const onPressSwitchAccount = useCallback(
async (acct: AccountData) => {
track('Settings:SwitchAccountButtonClicked')
setIsSwitching(true)
const success = await store.session.resumeSession(acct)
store.shell.closeAllActiveElements()
if (success) {
resetNavigation()
Toast.show(`Signed in as ${acct.displayName || acct.handle}`)
} else {
Toast.show('Sorry! We need you to enter your password.')
navigation.navigate('HomeTab')
navigation.dispatch(StackActions.popToTop())
store.session.clear()
}
},
[track, setIsSwitching, navigation, store],
)
return [isSwitching, setIsSwitching, onPressSwitchAccount]
}
@@ -1,11 +1,11 @@
import React from 'react'
import {AppBskyActorDefs} from '@atproto/api'
import {useStores} from 'state/index'
import {FollowState} from 'state/models/cache/my-follows'
export function useFollowDid({did}: {did: string}) {
export function useFollowProfile(profile: AppBskyActorDefs.ProfileViewBasic) {
const store = useStores()
const state = store.me.follows.getFollowState(did)
const state = store.me.follows.getFollowState(profile.did)
return {
state,
@@ -13,8 +13,10 @@ export function useFollowDid({did}: {did: string}) {
toggle: React.useCallback(async () => {
if (state === FollowState.Following) {
try {
await store.agent.deleteFollow(store.me.follows.getFollowUri(did))
store.me.follows.removeFollow(did)
await store.agent.deleteFollow(
store.me.follows.getFollowUri(profile.did),
)
store.me.follows.removeFollow(profile.did)
return {
state: FollowState.NotFollowing,
following: false,
@@ -25,8 +27,14 @@ export function useFollowDid({did}: {did: string}) {
}
} else if (state === FollowState.NotFollowing) {
try {
const res = await store.agent.follow(did)
store.me.follows.addFollow(did, res.uri)
const res = await store.agent.follow(profile.did)
store.me.follows.addFollow(profile.did, {
followRecordUri: res.uri,
did: profile.did,
handle: profile.handle,
displayName: profile.displayName,
avatar: profile.avatar,
})
return {
state: FollowState.Following,
following: true,
@@ -41,6 +49,6 @@ export function useFollowDid({did}: {did: string}) {
state: FollowState.Unknown,
following: false,
}
}, [store, did, state]),
}, [store, profile, state]),
}
}
+14 -10
View File
@@ -2,12 +2,18 @@ import {useState, useCallback, useRef} from 'react'
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
import {RootStoreModel} from 'state/index'
import {s} from 'lib/styles'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from './useWebMediaQueries'
const DY_LIMIT_UP = isDesktopWeb ? 30 : 10
const DY_LIMIT_DOWN = isDesktopWeb ? 150 : 10
const Y_LIMIT = 10
const useDeviceLimits = () => {
const {isDesktop} = useWebMediaQueries()
return {
dyLimitUp: isDesktop ? 30 : 10,
dyLimitDown: isDesktop ? 150 : 10,
}
}
export type OnScrollCb = (
event: NativeSyntheticEvent<NativeScrollEvent>,
) => void
@@ -18,6 +24,8 @@ export function useOnMainScroll(
): [OnScrollCb, boolean, ResetCb] {
let lastY = useRef(0)
let [isScrolledDown, setIsScrolledDown] = useState(false)
const {dyLimitUp, dyLimitDown} = useDeviceLimits()
return [
useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
@@ -25,15 +33,11 @@ export function useOnMainScroll(
const dy = y - (lastY.current || 0)
lastY.current = y
if (
!store.shell.minimalShellMode &&
dy > DY_LIMIT_DOWN &&
y > Y_LIMIT
) {
if (!store.shell.minimalShellMode && dy > dyLimitDown && y > Y_LIMIT) {
store.shell.setMinimalShellMode(true)
} else if (
store.shell.minimalShellMode &&
(dy < DY_LIMIT_UP * -1 || y <= Y_LIMIT)
(dy < dyLimitUp * -1 || y <= Y_LIMIT)
) {
store.shell.setMinimalShellMode(false)
}
@@ -50,7 +54,7 @@ export function useOnMainScroll(
setIsScrolledDown(false)
}
},
[store, isScrolledDown],
[store.shell, dyLimitDown, dyLimitUp, isScrolledDown],
),
isScrolledDown,
useCallback(() => {
+14 -3
View File
@@ -1,8 +1,19 @@
import {isAndroid} from 'platform/detection'
import {BackHandler} from 'react-native'
import {RootStoreModel} from 'state/index'
export function init(store: RootStoreModel) {
BackHandler.addEventListener('hardwareBackPress', () => {
return store.shell.closeAnyActiveElement()
})
// only register back handler on android, otherwise it throws an error
if (isAndroid) {
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
() => {
return store.shell.closeAnyActiveElement()
},
)
return () => {
backHandler.remove()
}
}
return () => {}
}
+17
View File
@@ -15,3 +15,20 @@ export function enforceLen(str: string, len: number, ellipsis = false): string {
}
return str
}
// https://stackoverflow.com/a/52171480
export function toHashCode(str: string, seed = 0): number {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i)
h1 = Math.imul(h1 ^ ch, 2654435761)
h2 = Math.imul(h2 ^ ch, 1597334677)
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909)
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909)
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
}
+51
View File
@@ -1,6 +1,7 @@
import {AtUri} from '@atproto/api'
import {PROD_SERVICE} from 'state/index'
import TLDs from 'tlds'
import psl from 'psl'
export function isValidDomain(str: string): boolean {
return !!TLDs.find(tld => {
@@ -166,3 +167,53 @@ export function getYoutubeVideoId(link: string): string | undefined {
}
return videoId
}
export function linkRequiresWarning(uri: string, label: string) {
const labelDomain = labelToDomain(label)
if (!labelDomain) {
return true
}
try {
const urip = new URL(uri)
return labelDomain !== urip.hostname
} catch {
return true
}
}
function labelToDomain(label: string): string | undefined {
// any spaces just immediately consider the label a non-url
if (/\s/.test(label)) {
return undefined
}
try {
return new URL(label).hostname
} catch {}
try {
return new URL('https://' + label).hostname
} catch {}
return undefined
}
export function isPossiblyAUrl(str: string): boolean {
str = str.trim()
if (str.startsWith('http://')) {
return true
}
if (str.startsWith('https://')) {
return true
}
const [firstWord] = str.split(/[\s\/]/)
return isValidDomain(firstWord)
}
export function splitApexDomain(hostname: string): [string, string] {
const hostnamep = psl.parse(hostname)
if (hostnamep.error || !hostnamep.listed || !hostnamep.domain) {
return ['', hostname]
}
return [
hostnamep.subdomain ? `${hostnamep.subdomain}.` : '',
hostnamep.domain,
]
}
+2 -2
View File
@@ -264,8 +264,8 @@ export const defaultTheme: Theme = {
fontWeight: '400',
},
'post-text-lg': {
fontSize: 22,
letterSpacing: 0.4,
fontSize: 20,
letterSpacing: 0.2,
fontWeight: '400',
},
'button-lg': {