Merge branch 'main' into hailey/search-improvements
This commit is contained in:
@@ -1,15 +1,28 @@
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedGetAuthorFeed as GetAuthorFeed,
|
||||
BskyAgent,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {getAgent} from '#/state/session'
|
||||
|
||||
export class AuthorFeedAPI implements FeedAPI {
|
||||
constructor(public params: GetAuthorFeed.QueryParams) {}
|
||||
getAgent: () => BskyAgent
|
||||
params: GetAuthorFeed.QueryParams
|
||||
|
||||
constructor({
|
||||
getAgent,
|
||||
feedParams,
|
||||
}: {
|
||||
getAgent: () => BskyAgent
|
||||
feedParams: GetAuthorFeed.QueryParams
|
||||
}) {
|
||||
this.getAgent = getAgent
|
||||
this.params = feedParams
|
||||
}
|
||||
|
||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||
const res = await getAgent().getAuthorFeed({
|
||||
const res = await this.getAgent().getAuthorFeed({
|
||||
...this.params,
|
||||
limit: 1,
|
||||
})
|
||||
@@ -23,7 +36,7 @@ export class AuthorFeedAPI implements FeedAPI {
|
||||
cursor: string | undefined
|
||||
limit: number
|
||||
}): Promise<FeedAPIResponse> {
|
||||
const res = await getAgent().getAuthorFeed({
|
||||
const res = await this.getAgent().getAuthorFeed({
|
||||
...this.params,
|
||||
cursor,
|
||||
limit,
|
||||
|
||||
@@ -2,18 +2,30 @@ import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedGetFeed as GetCustomFeed,
|
||||
AtpAgent,
|
||||
BskyAgent,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
|
||||
export class CustomFeedAPI implements FeedAPI {
|
||||
constructor(public params: GetCustomFeed.QueryParams) {}
|
||||
getAgent: () => BskyAgent
|
||||
params: GetCustomFeed.QueryParams
|
||||
|
||||
constructor({
|
||||
getAgent,
|
||||
feedParams,
|
||||
}: {
|
||||
getAgent: () => BskyAgent
|
||||
feedParams: GetCustomFeed.QueryParams
|
||||
}) {
|
||||
this.getAgent = getAgent
|
||||
this.params = feedParams
|
||||
}
|
||||
|
||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const res = await getAgent().app.bsky.feed.getFeed(
|
||||
const res = await this.getAgent().app.bsky.feed.getFeed(
|
||||
{
|
||||
...this.params,
|
||||
limit: 1,
|
||||
@@ -31,15 +43,19 @@ export class CustomFeedAPI implements FeedAPI {
|
||||
limit: number
|
||||
}): Promise<FeedAPIResponse> {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const agent = getAgent()
|
||||
const agent = this.getAgent()
|
||||
const res = agent.session
|
||||
? await getAgent().app.bsky.feed.getFeed(
|
||||
? await this.getAgent().app.bsky.feed.getFeed(
|
||||
{
|
||||
...this.params,
|
||||
cursor,
|
||||
limit,
|
||||
},
|
||||
{headers: {'Accept-Language': contentLangs}},
|
||||
{
|
||||
headers: {
|
||||
'Accept-Language': contentLangs,
|
||||
},
|
||||
},
|
||||
)
|
||||
: await loggedOutFetch({...this.params, cursor, limit})
|
||||
if (res.success) {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {AppBskyFeedDefs, BskyAgent} from '@atproto/api'
|
||||
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {getAgent} from '#/state/session'
|
||||
|
||||
export class FollowingFeedAPI implements FeedAPI {
|
||||
constructor() {}
|
||||
getAgent: () => BskyAgent
|
||||
|
||||
constructor({getAgent}: {getAgent: () => BskyAgent}) {
|
||||
this.getAgent = getAgent
|
||||
}
|
||||
|
||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||
const res = await getAgent().getTimeline({
|
||||
const res = await this.getAgent().getTimeline({
|
||||
limit: 1,
|
||||
})
|
||||
return res.data.feed[0]
|
||||
@@ -19,7 +23,7 @@ export class FollowingFeedAPI implements FeedAPI {
|
||||
cursor: string | undefined
|
||||
limit: number
|
||||
}): Promise<FeedAPIResponse> {
|
||||
const res = await getAgent().getTimeline({
|
||||
const res = await this.getAgent().getTimeline({
|
||||
cursor,
|
||||
limit,
|
||||
})
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {FollowingFeedAPI} from './following'
|
||||
import {CustomFeedAPI} from './custom'
|
||||
import {AppBskyFeedDefs, BskyAgent} from '@atproto/api'
|
||||
|
||||
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
||||
import {CustomFeedAPI} from './custom'
|
||||
import {FollowingFeedAPI} from './following'
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
|
||||
// HACK
|
||||
// the feed API does not include any facilities for passing down
|
||||
@@ -26,19 +27,27 @@ export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = {
|
||||
}
|
||||
|
||||
export class HomeFeedAPI implements FeedAPI {
|
||||
getAgent: () => BskyAgent
|
||||
following: FollowingFeedAPI
|
||||
discover: CustomFeedAPI
|
||||
usingDiscover = false
|
||||
itemCursor = 0
|
||||
|
||||
constructor() {
|
||||
this.following = new FollowingFeedAPI()
|
||||
this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')})
|
||||
constructor({getAgent}: {getAgent: () => BskyAgent}) {
|
||||
this.getAgent = getAgent
|
||||
this.following = new FollowingFeedAPI({getAgent})
|
||||
this.discover = new CustomFeedAPI({
|
||||
getAgent,
|
||||
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
|
||||
})
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.following = new FollowingFeedAPI()
|
||||
this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')})
|
||||
this.following = new FollowingFeedAPI({getAgent: this.getAgent})
|
||||
this.discover = new CustomFeedAPI({
|
||||
getAgent: this.getAgent,
|
||||
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
|
||||
})
|
||||
this.usingDiscover = false
|
||||
this.itemCursor = 0
|
||||
}
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedGetActorLikes as GetActorLikes,
|
||||
BskyAgent,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {getAgent} from '#/state/session'
|
||||
|
||||
export class LikesFeedAPI implements FeedAPI {
|
||||
constructor(public params: GetActorLikes.QueryParams) {}
|
||||
getAgent: () => BskyAgent
|
||||
params: GetActorLikes.QueryParams
|
||||
|
||||
constructor({
|
||||
getAgent,
|
||||
feedParams,
|
||||
}: {
|
||||
getAgent: () => BskyAgent
|
||||
feedParams: GetActorLikes.QueryParams
|
||||
}) {
|
||||
this.getAgent = getAgent
|
||||
this.params = feedParams
|
||||
}
|
||||
|
||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||
const res = await getAgent().getActorLikes({
|
||||
const res = await this.getAgent().getActorLikes({
|
||||
...this.params,
|
||||
limit: 1,
|
||||
})
|
||||
@@ -23,7 +36,7 @@ export class LikesFeedAPI implements FeedAPI {
|
||||
cursor: string | undefined
|
||||
limit: number
|
||||
}): Promise<FeedAPIResponse> {
|
||||
const res = await getAgent().getActorLikes({
|
||||
const res = await this.getAgent().getActorLikes({
|
||||
...this.params,
|
||||
cursor,
|
||||
limit,
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedGetListFeed as GetListFeed,
|
||||
BskyAgent,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {getAgent} from '#/state/session'
|
||||
|
||||
export class ListFeedAPI implements FeedAPI {
|
||||
constructor(public params: GetListFeed.QueryParams) {}
|
||||
getAgent: () => BskyAgent
|
||||
params: GetListFeed.QueryParams
|
||||
|
||||
constructor({
|
||||
getAgent,
|
||||
feedParams,
|
||||
}: {
|
||||
getAgent: () => BskyAgent
|
||||
feedParams: GetListFeed.QueryParams
|
||||
}) {
|
||||
this.getAgent = getAgent
|
||||
this.params = feedParams
|
||||
}
|
||||
|
||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||
const res = await getAgent().app.bsky.feed.getListFeed({
|
||||
const res = await this.getAgent().app.bsky.feed.getListFeed({
|
||||
...this.params,
|
||||
limit: 1,
|
||||
})
|
||||
@@ -23,7 +36,7 @@ export class ListFeedAPI implements FeedAPI {
|
||||
cursor: string | undefined
|
||||
limit: number
|
||||
}): Promise<FeedAPIResponse> {
|
||||
const res = await getAgent().app.bsky.feed.getListFeed({
|
||||
const res = await this.getAgent().app.bsky.feed.getListFeed({
|
||||
...this.params,
|
||||
cursor,
|
||||
limit,
|
||||
|
||||
+72
-17
@@ -1,31 +1,51 @@
|
||||
import {AppBskyFeedDefs, AppBskyFeedGetTimeline} from '@atproto/api'
|
||||
import {AppBskyFeedDefs, AppBskyFeedGetTimeline, BskyAgent} from '@atproto/api'
|
||||
import shuffle from 'lodash.shuffle'
|
||||
import {timeout} from 'lib/async/timeout'
|
||||
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {FeedParams} from '#/state/queries/post-feed'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
import {timeout} from 'lib/async/timeout'
|
||||
import {feedUriToHref} from 'lib/strings/url-helpers'
|
||||
import {FeedTuner} from '../feed-manip'
|
||||
import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
|
||||
import {FeedParams} from '#/state/queries/post-feed'
|
||||
import {FeedTunerFn} from '../feed-manip'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
|
||||
|
||||
const REQUEST_WAIT_MS = 500 // 500ms
|
||||
const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
|
||||
|
||||
export class MergeFeedAPI implements FeedAPI {
|
||||
getAgent: () => BskyAgent
|
||||
params: FeedParams
|
||||
feedTuners: FeedTunerFn[]
|
||||
following: MergeFeedSource_Following
|
||||
customFeeds: MergeFeedSource_Custom[] = []
|
||||
feedCursor = 0
|
||||
itemCursor = 0
|
||||
sampleCursor = 0
|
||||
|
||||
constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) {
|
||||
this.following = new MergeFeedSource_Following(this.feedTuners)
|
||||
constructor({
|
||||
getAgent,
|
||||
feedParams,
|
||||
feedTuners,
|
||||
}: {
|
||||
getAgent: () => BskyAgent
|
||||
feedParams: FeedParams
|
||||
feedTuners: FeedTunerFn[]
|
||||
}) {
|
||||
this.getAgent = getAgent
|
||||
this.params = feedParams
|
||||
this.feedTuners = feedTuners
|
||||
this.following = new MergeFeedSource_Following({
|
||||
getAgent: this.getAgent,
|
||||
feedTuners: this.feedTuners,
|
||||
})
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.following = new MergeFeedSource_Following(this.feedTuners)
|
||||
this.following = new MergeFeedSource_Following({
|
||||
getAgent: this.getAgent,
|
||||
feedTuners: this.feedTuners,
|
||||
})
|
||||
this.customFeeds = []
|
||||
this.feedCursor = 0
|
||||
this.itemCursor = 0
|
||||
@@ -33,7 +53,12 @@ export class MergeFeedAPI implements FeedAPI {
|
||||
if (this.params.mergeFeedSources) {
|
||||
this.customFeeds = shuffle(
|
||||
this.params.mergeFeedSources.map(
|
||||
feedUri => new MergeFeedSource_Custom(feedUri, this.feedTuners),
|
||||
feedUri =>
|
||||
new MergeFeedSource_Custom({
|
||||
getAgent: this.getAgent,
|
||||
feedUri,
|
||||
feedTuners: this.feedTuners,
|
||||
}),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
@@ -42,7 +67,7 @@ export class MergeFeedAPI implements FeedAPI {
|
||||
}
|
||||
|
||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||
const res = await getAgent().getTimeline({
|
||||
const res = await this.getAgent().getTimeline({
|
||||
limit: 1,
|
||||
})
|
||||
return res.data.feed[0]
|
||||
@@ -136,12 +161,23 @@ export class MergeFeedAPI implements FeedAPI {
|
||||
}
|
||||
|
||||
class MergeFeedSource {
|
||||
getAgent: () => BskyAgent
|
||||
feedTuners: FeedTunerFn[]
|
||||
sourceInfo: ReasonFeedSource | undefined
|
||||
cursor: string | undefined = undefined
|
||||
queue: AppBskyFeedDefs.FeedViewPost[] = []
|
||||
hasMore = true
|
||||
|
||||
constructor(public feedTuners: FeedTunerFn[]) {}
|
||||
constructor({
|
||||
getAgent,
|
||||
feedTuners,
|
||||
}: {
|
||||
getAgent: () => BskyAgent
|
||||
feedTuners: FeedTunerFn[]
|
||||
}) {
|
||||
this.getAgent = getAgent
|
||||
this.feedTuners = feedTuners
|
||||
}
|
||||
|
||||
get numReady() {
|
||||
return this.queue.length
|
||||
@@ -203,7 +239,7 @@ class MergeFeedSource_Following extends MergeFeedSource {
|
||||
cursor: string | undefined,
|
||||
limit: number,
|
||||
): Promise<AppBskyFeedGetTimeline.Response> {
|
||||
const res = await getAgent().getTimeline({cursor, limit})
|
||||
const res = await this.getAgent().getTimeline({cursor, limit})
|
||||
// run the tuner pre-emptively to ensure better mixing
|
||||
const slices = this.tuner.tune(res.data.feed, {
|
||||
dryRun: false,
|
||||
@@ -215,10 +251,25 @@ class MergeFeedSource_Following extends MergeFeedSource {
|
||||
}
|
||||
|
||||
class MergeFeedSource_Custom extends MergeFeedSource {
|
||||
getAgent: () => BskyAgent
|
||||
minDate: Date
|
||||
feedUri: string
|
||||
|
||||
constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) {
|
||||
super(feedTuners)
|
||||
constructor({
|
||||
getAgent,
|
||||
feedUri,
|
||||
feedTuners,
|
||||
}: {
|
||||
getAgent: () => BskyAgent
|
||||
feedUri: string
|
||||
feedTuners: FeedTunerFn[]
|
||||
}) {
|
||||
super({
|
||||
getAgent,
|
||||
feedTuners,
|
||||
})
|
||||
this.getAgent = getAgent
|
||||
this.feedUri = feedUri
|
||||
this.sourceInfo = {
|
||||
$type: 'reasonFeedSource',
|
||||
uri: feedUri,
|
||||
@@ -233,13 +284,17 @@ class MergeFeedSource_Custom extends MergeFeedSource {
|
||||
): Promise<AppBskyFeedGetTimeline.Response> {
|
||||
try {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const res = await getAgent().app.bsky.feed.getFeed(
|
||||
const res = await this.getAgent().app.bsky.feed.getFeed(
|
||||
{
|
||||
cursor,
|
||||
limit,
|
||||
feed: this.feedUri,
|
||||
},
|
||||
{headers: {'Accept-Language': contentLangs}},
|
||||
{
|
||||
headers: {
|
||||
'Accept-Language': contentLangs,
|
||||
},
|
||||
},
|
||||
)
|
||||
// NOTE
|
||||
// some custom feeds fail to enforce the pagination limit
|
||||
|
||||
+4
-15
@@ -1,4 +1,3 @@
|
||||
import {deleteAsync} from 'expo-file-system'
|
||||
import {
|
||||
AppBskyEmbedExternal,
|
||||
AppBskyEmbedImages,
|
||||
@@ -20,6 +19,7 @@ import {shortenLinks} from 'lib/strings/rich-text-manip'
|
||||
import {isNative, isWeb} from 'platform/detection'
|
||||
import {ImageModel} from 'state/models/media/image'
|
||||
import {LinkMeta} from '../link-meta/link-meta'
|
||||
import {safeDeleteAsync} from '../media/manip'
|
||||
|
||||
export interface ExternalEmbedDraft {
|
||||
uri: string
|
||||
@@ -119,15 +119,9 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
|
||||
const {width, height} = image.compressed || image
|
||||
logger.debug(`Uploading image`)
|
||||
const res = await uploadBlob(agent, path, 'image/jpeg')
|
||||
|
||||
if (isNative) {
|
||||
try {
|
||||
deleteAsync(path)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
safeDeleteAsync(path)
|
||||
}
|
||||
|
||||
images.push({
|
||||
image: res.data.blob,
|
||||
alt: image.altText ?? '',
|
||||
@@ -182,13 +176,8 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
|
||||
encoding,
|
||||
)
|
||||
thumb = thumbUploadRes.data.blob
|
||||
|
||||
try {
|
||||
if (isNative) {
|
||||
deleteAsync(opts.extLink.localThumb.path)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
if (isNative) {
|
||||
safeDeleteAsync(opts.extLink.localThumb.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export const LOGIN_INCLUDE_DEV_SERVERS = true
|
||||
export const PWI_ENABLED = true
|
||||
export const NEW_ONBOARDING_ENABLED = true
|
||||
|
||||
@@ -4,6 +4,7 @@ export const LOCAL_DEV_SERVICE =
|
||||
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
|
||||
export const STAGING_SERVICE = 'https://staging.bsky.dev'
|
||||
export const BSKY_SERVICE = 'https://bsky.social'
|
||||
export const PUBLIC_BSKY_SERVICE = 'https://public.api.bsky.app'
|
||||
export const DEFAULT_SERVICE = BSKY_SERVICE
|
||||
const HELP_DESK_LANG = 'en-us'
|
||||
export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
|
||||
|
||||
+45
-16
@@ -1,13 +1,14 @@
|
||||
import RNFetchBlob from 'rn-fetch-blob'
|
||||
import ImageResizer from '@bam.tech/react-native-image-resizer'
|
||||
import {Image as RNImage, Share as RNShare} from 'react-native'
|
||||
import {Image} from 'react-native-image-crop-picker'
|
||||
import * as RNFS from 'react-native-fs'
|
||||
import uuid from 'react-native-uuid'
|
||||
import * as Sharing from 'expo-sharing'
|
||||
import {cacheDirectory, copyAsync, deleteAsync} from 'expo-file-system'
|
||||
import * as MediaLibrary from 'expo-media-library'
|
||||
import {Dimensions} from './types'
|
||||
import * as Sharing from 'expo-sharing'
|
||||
import ImageResizer from '@bam.tech/react-native-image-resizer'
|
||||
import RNFetchBlob from 'rn-fetch-blob'
|
||||
|
||||
import {isAndroid, isIOS} from 'platform/detection'
|
||||
import {Dimensions} from './types'
|
||||
|
||||
export async function compressIfNeeded(
|
||||
img: Image,
|
||||
@@ -23,7 +24,10 @@ export async function compressIfNeeded(
|
||||
mode: 'stretch',
|
||||
maxSize,
|
||||
})
|
||||
const finalImageMovedPath = await moveToPermanentPath(resizedImage.path)
|
||||
const finalImageMovedPath = await moveToPermanentPath(
|
||||
resizedImage.path,
|
||||
'.jpg',
|
||||
)
|
||||
const finalImg = {
|
||||
...resizedImage,
|
||||
path: finalImageMovedPath,
|
||||
@@ -63,13 +67,15 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
|
||||
downloadRes = await downloadResPromise
|
||||
clearTimeout(to1)
|
||||
|
||||
let localUri = downloadRes.path()
|
||||
if (!localUri.startsWith('file://')) {
|
||||
localUri = `file://${localUri}`
|
||||
const status = downloadRes.info().status
|
||||
if (status !== 200) {
|
||||
return
|
||||
}
|
||||
|
||||
const localUri = normalizePath(downloadRes.path(), true)
|
||||
return await doResize(localUri, opts)
|
||||
} finally {
|
||||
// TODO Whenever we remove `rn-fetch-blob`, we will need to replace this `flush()` with a `deleteAsync()` -hailey
|
||||
if (downloadRes) {
|
||||
downloadRes.flush()
|
||||
}
|
||||
@@ -105,7 +111,8 @@ export async function shareImageModal({uri}: {uri: string}) {
|
||||
UTI: 'image/png',
|
||||
})
|
||||
}
|
||||
RNFS.unlink(imagePath)
|
||||
|
||||
safeDeleteAsync(imagePath)
|
||||
}
|
||||
|
||||
export async function saveImageToMediaLibrary({uri}: {uri: string}) {
|
||||
@@ -122,6 +129,7 @@ export async function saveImageToMediaLibrary({uri}: {uri: string}) {
|
||||
|
||||
// save
|
||||
await MediaLibrary.createAssetAsync(imagePath)
|
||||
safeDeleteAsync(imagePath)
|
||||
}
|
||||
|
||||
export function getImageDim(path: string): Promise<Dimensions> {
|
||||
@@ -168,6 +176,8 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
||||
width: resizeRes.width,
|
||||
height: resizeRes.height,
|
||||
}
|
||||
} else {
|
||||
safeDeleteAsync(resizeRes.path)
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
@@ -175,7 +185,7 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
||||
)
|
||||
}
|
||||
|
||||
async function moveToPermanentPath(path: string, ext = ''): Promise<string> {
|
||||
async function moveToPermanentPath(path: string, ext = 'jpg'): Promise<string> {
|
||||
/*
|
||||
Since this package stores images in a temp directory, we need to move the file to a permanent location.
|
||||
Relevant: IOS bug when trying to open a second time:
|
||||
@@ -183,14 +193,33 @@ async function moveToPermanentPath(path: string, ext = ''): Promise<string> {
|
||||
*/
|
||||
const filename = uuid.v4()
|
||||
|
||||
const destinationPath = joinPath(
|
||||
RNFS.TemporaryDirectoryPath,
|
||||
`${filename}${ext}`,
|
||||
)
|
||||
await RNFS.moveFile(path, destinationPath)
|
||||
// cacheDirectory will not ever be null on native, but it could be on web. This function only ever gets called on
|
||||
// native so we assert as a string.
|
||||
const destinationPath = joinPath(cacheDirectory as string, filename + ext)
|
||||
await copyAsync({
|
||||
from: normalizePath(path),
|
||||
to: normalizePath(destinationPath),
|
||||
})
|
||||
safeDeleteAsync(path)
|
||||
return normalizePath(destinationPath)
|
||||
}
|
||||
|
||||
export async function safeDeleteAsync(path: string) {
|
||||
// Normalize is necessary for Android, otherwise it doesn't delete.
|
||||
const normalizedPath = normalizePath(path)
|
||||
try {
|
||||
await Promise.allSettled([
|
||||
deleteAsync(normalizedPath, {idempotent: true}),
|
||||
// HACK: Try this one too. Might exist due to api-polyfill hack.
|
||||
deleteAsync(normalizedPath.replace(/\.jpe?g$/, '.bin'), {
|
||||
idempotent: true,
|
||||
}),
|
||||
])
|
||||
} catch (e) {
|
||||
console.error('Failed to delete file', e)
|
||||
}
|
||||
}
|
||||
|
||||
function joinPath(a: string, b: string) {
|
||||
if (a.endsWith('/')) {
|
||||
if (b.startsWith('/')) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {useEffect} from 'react'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import {BskyAgent} from '@atproto/api'
|
||||
import {QueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
|
||||
import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {getAgent, SessionAccount} from '#/state/session'
|
||||
import {SessionAccount} from '#/state/session'
|
||||
import {track} from 'lib/analytics/analytics'
|
||||
import {devicePlatform, isIOS} from 'platform/detection'
|
||||
import {resetToTab} from '../../Navigation'
|
||||
@@ -18,6 +19,7 @@ const SERVICE_DID = (serviceUrl?: string) =>
|
||||
: 'did:web:api.bsky.app'
|
||||
|
||||
export async function requestPermissionsAndRegisterToken(
|
||||
getAgent: () => BskyAgent,
|
||||
account: SessionAccount,
|
||||
) {
|
||||
// request notifications permission once the user has logged in
|
||||
@@ -49,6 +51,7 @@ export async function requestPermissionsAndRegisterToken(
|
||||
}
|
||||
|
||||
export function registerTokenChangeHandler(
|
||||
getAgent: () => BskyAgent,
|
||||
account: SessionAccount,
|
||||
): () => void {
|
||||
// listens for new changes to the push token
|
||||
|
||||
@@ -8,4 +8,3 @@ export type Gate =
|
||||
| 'start_session_with_following_v2'
|
||||
| 'test_gate_1'
|
||||
| 'test_gate_2'
|
||||
| 'use_new_suggestions_endpoint'
|
||||
|
||||
@@ -352,8 +352,17 @@ export function parseEmbedPlayerFromUrl(
|
||||
|
||||
if (id && filename && dimensions && id.includes('AAAAC')) {
|
||||
if (Platform.OS === 'web') {
|
||||
id = id.replace('AAAAC', 'AAAP3')
|
||||
filename = filename.replace('.gif', '.webm')
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(
|
||||
navigator.userAgent,
|
||||
)
|
||||
|
||||
if (isSafari) {
|
||||
id = id.replace('AAAAC', 'AAAP1')
|
||||
filename = filename.replace('.gif', '.mp4')
|
||||
} else {
|
||||
id = id.replace('AAAAC', 'AAAP3')
|
||||
filename = filename.replace('.gif', '.webm')
|
||||
}
|
||||
} else {
|
||||
id = id.replace('AAAAC', 'AAAAM')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user