Skip to content

Commit

Permalink
Merge branch 'main' into feat/threading-2
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Sep 30, 2024
2 parents dbe8f82 + 587c0c6 commit 7c5a182
Show file tree
Hide file tree
Showing 108 changed files with 1,005 additions and 3,475 deletions.
15 changes: 0 additions & 15 deletions jest/jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,6 @@ jest.mock('expo-image-manipulator', () => ({
SaveFormat: jest.requireActual('expo-image-manipulator').SaveFormat,
}))

jest.mock('@segment/analytics-react-native', () => ({
createClient: () => ({
add: jest.fn(),
}),
useAnalytics: () => ({
track: jest.fn(),
identify: jest.fn(),
reset: jest.fn(),
group: jest.fn(),
screen: jest.fn(),
alias: jest.fn(),
flush: jest.fn(),
}),
}))

jest.mock('expo-camera', () => ({
Camera: {
useCameraPermissions: jest.fn(() => [true]),
Expand Down
2 changes: 1 addition & 1 deletion modules/Share-with-Bluesky/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
</dict>
</plist>
</plist>
51 changes: 44 additions & 7 deletions modules/Share-with-Bluesky/ShareViewController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import UIKit

let IMAGE_EXTENSIONS: [String] = ["png", "jpg", "jpeg", "gif", "heic"]
let MOVIE_EXTENSIONS: [String] = ["mov", "mp4", "m4v"]

enum URLType: String, CaseIterable {
case image
case movie
case other
}

class ShareViewController: UIViewController {
// This allows other forks to use this extension while also changing their
// scheme.
Expand All @@ -23,7 +32,7 @@ class ShareViewController: UIViewController {
await self.handleUrl(item: firstAttachment)
} else if firstAttachment.hasItemConformingToTypeIdentifier("public.image") {
await self.handleImages(items: attachments)
} else if firstAttachment.hasItemConformingToTypeIdentifier("public.video") {
} else if firstAttachment.hasItemConformingToTypeIdentifier("public.movie") {
await self.handleVideos(items: attachments)
} else {
self.completeRequest()
Expand All @@ -43,9 +52,18 @@ class ShareViewController: UIViewController {

private func handleUrl(item: NSItemProvider) async {
if let data = try? await item.loadItem(forTypeIdentifier: "public.url") as? URL {
if let encoded = data.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)") {
_ = self.openURL(url)
switch data.type {
case .image:
await handleImages(items: [item])
return
case .movie:
await handleVideos(items: [item])
return
case .other:
if let encoded = data.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)") {
_ = self.openURL(url)
}
}
}
self.completeRequest()
Expand Down Expand Up @@ -101,13 +119,13 @@ class ShareViewController: UIViewController {
private func handleVideos(items: [NSItemProvider]) async {
let firstItem = items.first

if let dataUri = try? await firstItem?.loadItem(forTypeIdentifier: "public.video") as? URL {
if let dataUri = try? await firstItem?.loadItem(forTypeIdentifier: "public.movie") as? URL {
let ext = String(dataUri.lastPathComponent.split(separator: ".").last ?? "mp4")
if let tempUrl = getTempUrl(ext: ext) {
let data = try? Data(contentsOf: dataUri)
try? data?.write(to: tempUrl)

if let encoded = dataUri.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
if let encoded = tempUrl.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?videoUri=\(encoded)") {
_ = self.openURL(url)
}
Expand Down Expand Up @@ -150,10 +168,29 @@ class ShareViewController: UIViewController {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application.perform(#selector(openURL(_:)), with: url) != nil
application.open(url)
return true
}
responder = responder?.next
}
return false
}
}

extension URL {
var type: URLType {
get {
guard self.absoluteString.starts(with: "file://"),
let ext = self.pathComponents.last?.split(separator: ".").last?.lowercased() else {
return .other
}

if IMAGE_EXTENSIONS.contains(ext) {
return .image
} else if MOVIE_EXTENSIONS.contains(ext) {
return .movie
}
return .other
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import java.io.File
import java.io.FileOutputStream
import java.net.URLEncoder

enum class AttachmentType {
IMAGE,
VIDEO,
}

class ExpoReceiveAndroidIntentsModule : Module() {
override fun definition() =
ModuleDefinition {
Expand All @@ -23,17 +28,26 @@ class ExpoReceiveAndroidIntentsModule : Module() {
}

private fun handleIntent(intent: Intent?) {
if (appContext.currentActivity == null || intent == null) return

if (intent.action == Intent.ACTION_SEND) {
if (intent.type == "text/plain") {
handleTextIntent(intent)
} else if (intent.type.toString().startsWith("image/")) {
handleImageIntent(intent)
if (appContext.currentActivity == null) return
intent?.let {
if (it.action == Intent.ACTION_SEND && it.type == "text/plain") {
handleTextIntent(it)
return
}
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
if (intent.type.toString().startsWith("image/")) {
handleImagesIntent(intent)

val type =
if (it.type.toString().startsWith("image/")) {
AttachmentType.IMAGE
} else if (it.type.toString().startsWith("video/")) {
AttachmentType.VIDEO
} else {
return
}

if (it.action == Intent.ACTION_SEND) {
handleAttachmentIntent(it, type)
} else if (it.action == Intent.ACTION_SEND_MULTIPLE) {
handleAttachmentsIntent(it, type)
}
}
}
Expand All @@ -48,26 +62,46 @@ class ExpoReceiveAndroidIntentsModule : Module() {
}
}

private fun handleImageIntent(intent: Intent) {
private fun handleAttachmentIntent(
intent: Intent,
type: AttachmentType,
) {
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
} else {
intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
if (uri == null) return

handleImageIntents(listOf(uri))
uri?.let {
when (type) {
AttachmentType.IMAGE -> handleImageIntents(listOf(it))
AttachmentType.VIDEO -> handleVideoIntents(listOf(it))
}
}
}

private fun handleImagesIntent(intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let {
handleImageIntents(it.filterIsInstance<Uri>().take(4))
private fun handleAttachmentsIntent(
intent: Intent,
type: AttachmentType,
) {
val uris =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent
.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)
?.filterIsInstance<Uri>()
?.take(4)
} else {
intent
.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
?.filterIsInstance<Uri>()
?.take(4)
}
} else {
intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.let {
handleImageIntents(it.filterIsInstance<Uri>().take(4))

uris?.let {
when (type) {
AttachmentType.IMAGE -> handleImageIntents(it)
else -> return
}
}
}
Expand All @@ -93,11 +127,33 @@ class ExpoReceiveAndroidIntentsModule : Module() {
}
}

private fun handleVideoIntents(uris: List<Uri>) {
val uri = uris[0]
// If there is no extension for the file, substringAfterLast returns the original string - not
// null, so we check for that below
// It doesn't actually matter what the extension is, so defaulting to mp4 is fine, even if the
// video isn't actually an mp4
var extension = uri.path?.substringAfterLast(".")
if (extension == null || extension == uri.path) {
extension = "mp4"
}
val file = createFile(extension)

val out = FileOutputStream(file)
appContext.currentActivity?.contentResolver?.openInputStream(uri)?.use {
it.copyTo(out)
}
"bluesky://intent/compose?videoUri=${URLEncoder.encode(file.path, "UTF-8")}".toUri().let {
val newIntent = Intent(Intent.ACTION_VIEW, it)
appContext.currentActivity?.startActivity(newIntent)
}
}

private fun getImageInfo(uri: Uri): Map<String, Any> {
val bitmap = MediaStore.Images.Media.getBitmap(appContext.currentActivity?.contentResolver, uri)
// We have to save this so that we can access it later when uploading the image.
// createTempFile will automatically place a unique string between "img" and "temp.jpeg"
val file = File.createTempFile("img", "temp.jpeg", appContext.currentActivity?.cacheDir)
val file = createFile("jpeg")
val out = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
out.flush()
Expand All @@ -110,6 +166,8 @@ class ExpoReceiveAndroidIntentsModule : Module() {
)
}

private fun createFile(extension: String): File = File.createTempFile(extension, "temp.$extension", appContext.currentActivity?.cacheDir)

// We will pas the width and height to the app here, since getting measurements
// on the RN side is a bit more involved, and we already have them here anyway.
private fun buildUriData(info: Map<String, Any>): String {
Expand Down
6 changes: 1 addition & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"icons:optimize": "svgo -f ./assets/icons"
},
"dependencies": {
"@atproto/api": "^0.13.7",
"@atproto/api": "^0.13.8",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
"@emoji-mart/react": "^1.1.1",
Expand Down Expand Up @@ -82,10 +82,6 @@
"@react-navigation/drawer": "^6.6.15",
"@react-navigation/native": "^6.1.17",
"@react-navigation/native-stack": "^6.9.26",
"@segment/analytics-next": "^1.51.3",
"@segment/analytics-react": "^1.0.0-rc1",
"@segment/analytics-react-native": "^2.10.1",
"@segment/sovran-react-native": "^0.4.5",
"@sentry/react-native": "5.32.0",
"@tamagui/focus-scope": "^1.84.1",
"@tanstack/query-async-storage-persister": "^5.25.0",
Expand Down
23 changes: 23 additions & 0 deletions plugins/shareExtension/withIntentFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ const withIntentFilters = config => {
},
],
},
{
action: [
{
$: {
'android:name': 'android.intent.action.SEND',
},
},
],
category: [
{
$: {
'android:name': 'android.intent.category.DEFAULT',
},
},
],
data: [
{
$: {
'android:mimeType': 'video/*',
},
},
],
},
{
action: [
{
Expand Down
3 changes: 0 additions & 3 deletions src/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
StackActions,
} from '@react-navigation/native'

import {init as initAnalytics} from '#/lib/analytics/analytics'
import {timeout} from '#/lib/async/timeout'
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
import {usePalette} from '#/lib/hooks/usePalette'
Expand Down Expand Up @@ -647,8 +646,6 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {

function onReady() {
prevLoggedRouteName.current = getCurrentRouteName()
initAnalytics(currentAccount)

if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) {
openModal({name: 'verify-email', showReminder: true})
snoozeEmailConfirmationPrompt()
Expand Down
9 changes: 6 additions & 3 deletions src/alf/fonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,18 @@ export function applyFonts(
// '100': 'Inter-Thin',
// '200': 'Inter-ExtraLight',
// '300': 'Inter-Light',
// '500': 'Inter-Medium',
// '700': 'Inter-Bold',
// '900': 'Inter-Black',
'100': 'Inter-Regular',
'200': 'Inter-Regular',
'300': 'Inter-Regular',
'400': 'Inter-Regular',
'500': 'Inter-Medium',
'500': 'Inter-SemiBold',
'600': 'Inter-SemiBold',
'700': 'Inter-Bold',
'700': 'Inter-SemiBold',
'800': 'Inter-ExtraBold',
'900': 'Inter-Black',
'900': 'Inter-ExtraBold',
}[style.fontWeight as string] || 'Inter-Regular'

if (style.fontStyle === 'italic') {
Expand Down
9 changes: 9 additions & 0 deletions src/alf/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ export const fontWeight = {
} as const

export const gradients = {
primary: {
values: [
[0, '#054CFF'],
[0.4, '#1085FE'],
[0.6, '#1085FE'],
[1, '#59B9FF'],
],
hover_value: '#1085FE',
},
sky: {
values: [
[0, '#0A7AFF'],
Expand Down
6 changes: 4 additions & 2 deletions src/components/AccountList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,12 @@ function AccountItem({
<UserAvatar avatar={profile?.avatar} size={24} />
</View>
<Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}>
<Text style={[a.font_bold]}>
<Text emoji style={[a.font_bold]}>
{profile?.displayName || account.handle}{' '}
</Text>
<Text style={[t.atoms.text_contrast_medium]}>{account.handle}</Text>
<Text emoji style={[t.atoms.text_contrast_medium]}>
{account.handle}
</Text>
</Text>
{isCurrentAccount ? (
<Check
Expand Down
Loading

0 comments on commit 7c5a182

Please sign in to comment.