diff --git a/.github/workflows/bundle-deploy-eas-update.yml b/.github/workflows/bundle-deploy-eas-update.yml index a684f525fd..d61360d7de 100644 --- a/.github/workflows/bundle-deploy-eas-update.yml +++ b/.github/workflows/bundle-deploy-eas-update.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - starter-packs workflow_dispatch: inputs: channel: diff --git a/app.config.js b/app.config.js index 7c0e30f6e1..bd8598ef08 100644 --- a/app.config.js +++ b/app.config.js @@ -92,9 +92,6 @@ module.exports = function (config) { }, entitlements: { 'com.apple.security.application-groups': 'group.app.bsky', - 'com.apple.developer.associated-appclip-app-identifiers': [ - 'xyz.blueskyweb.app.AppClip', - ], }, privacyManifests: { NSPrivacyAccessedAPITypes: [ @@ -207,21 +204,7 @@ module.exports = function (config) { sounds: PLATFORM === 'ios' ? ['assets/dm.aiff'] : ['assets/dm.mp3'], }, ], - [ - 'react-native-app-clip', - { - name: 'BlueskyAppClip', - groupIdentifier: 'group.app.bsky', - excludedPackages: [ - '@bam.tech/react-native-image-resizer', - '@braintree/sanitize-url', - '@discord/bottom-sheet', - '@emoji-mart/react', - '@floating-ui/dom', - '@floating-ui/react-dom', - ], - }, - ], + './plugins/starterPackAppClipExtension/withStarterPackAppClip.js', './plugins/withAndroidManifestPlugin.js', './plugins/withAndroidManifestFCMIconPlugin.js', './plugins/withAndroidStylesWindowBackgroundPlugin.js', @@ -255,19 +238,8 @@ module.exports = function (config) { }, }, { - targetName: 'BlueskyAppClip', + targetName: 'BlueskyClip', bundleIdentifier: 'xyz.blueskyweb.app.AppClip', - entitlements: { - 'com.apple.security.application-groups': [ - 'group.app.bsky', - ], - 'com.apple.developer.associated-domains': - ASSOCIATED_DOMAINS, - 'com.apple.developer.on-demand-install-capable': true, - 'com.apple.developer.parent-application-identifiers': [ - 'xyz.blueskyweb.app', - ], - }, }, ], }, diff --git a/bskyweb/static/.well-known/apple-app-site-association b/bskyweb/static/.well-known/apple-app-site-association index 232acdf255..32e63532de 100644 --- a/bskyweb/static/.well-known/apple-app-site-association +++ b/bskyweb/static/.well-known/apple-app-site-association @@ -1,6 +1,9 @@ { "applinks": { "apps": [], + "appclips": { + "apps": ["B3LX46C5HS.xyz.blueskyweb.app.AppClip"] + }, "details": [ { "appID": "B3LX46C5HS.xyz.blueskyweb.app", @@ -10,4 +13,4 @@ } ] } -} \ No newline at end of file +} diff --git a/eas.json b/eas.json index 708339ce30..a705c40027 100644 --- a/eas.json +++ b/eas.json @@ -63,7 +63,7 @@ "testflight": { "extends": "base", "ios": { - "autoIncrement": false + "autoIncrement": true }, "android": { "autoIncrement": true diff --git a/modules/BlueskyClip/AppDelegate.swift b/modules/BlueskyClip/AppDelegate.swift new file mode 100644 index 0000000000..1b02eea07c --- /dev/null +++ b/modules/BlueskyClip/AppDelegate.swift @@ -0,0 +1,17 @@ +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + self.window = UIWindow() + + let controller = ViewController() + + self.window?.rootViewController = controller + self.window?.makeKeyAndVisible() + + return true + } +} diff --git a/modules/BlueskyClip/ViewController.swift b/modules/BlueskyClip/ViewController.swift new file mode 100644 index 0000000000..5a3b78f04e --- /dev/null +++ b/modules/BlueskyClip/ViewController.swift @@ -0,0 +1,20 @@ +import UIKit +import WebKit + +class ViewController: UIViewController { + var webView: WKWebView? + + override func viewDidLoad() { + super.viewDidLoad() + + let webView = WKWebView(frame: self.view.bounds) + webView.translatesAutoresizingMaskIntoConstraints = false + webView.contentMode = .scaleToFill + + self.view.addSubview(webView) + + if let url = URL(string: "http://localhost:19006/start/haileyok.com/3kuw7byr7gd2x?clip=true") { + webView.load(URLRequest(url: url)) + } + } +} diff --git a/modules/BlueskyNSE/dm.aiff b/modules/BlueskyNSE/dm.aiff new file mode 100644 index 0000000000..364b814b7f Binary files /dev/null and b/modules/BlueskyNSE/dm.aiff differ diff --git a/package.json b/package.json index 9e011fb66f..ae9f908038 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,6 @@ "react-dom": "^18.2.0", "react-keyed-flatten-children": "^3.0.0", "react-native": "0.74.1", - "react-native-app-clip": "bluesky-social/react-native-app-clip", "react-native-date-picker": "^4.4.2", "react-native-drawer-layout": "^4.0.0-alpha.3", "react-native-fs": "^2.20.0", diff --git a/plugins/starterPackAppClipExtension/withAppEntitlements.js b/plugins/starterPackAppClipExtension/withAppEntitlements.js new file mode 100644 index 0000000000..4ce81ea611 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withAppEntitlements.js @@ -0,0 +1,13 @@ +const {withEntitlementsPlist} = require('@expo/config-plugins') + +const withAppEntitlements = config => { + // eslint-disable-next-line no-shadow + return withEntitlementsPlist(config, async config => { + config.modResults['com.apple.security.application-groups'] = [ + `group.app.bsky`, + ] + return config + }) +} + +module.exports = {withAppEntitlements} diff --git a/plugins/starterPackAppClipExtension/withClipEntitlements.js b/plugins/starterPackAppClipExtension/withClipEntitlements.js new file mode 100644 index 0000000000..77636b5c94 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withClipEntitlements.js @@ -0,0 +1,32 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withClipEntitlements = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const entitlementsPath = path.join( + config.modRequest.platformProjectRoot, + targetName, + `${targetName}.entitlements`, + ) + + const appClipEntitlements = { + 'com.apple.security.application-groups': [`group.app.bsky`], + 'com.apple.developer.parent-application-identifiers': [ + `$(AppIdentifierPrefix)${config.ios.bundleIdentifier}`, + ], + 'com.apple.developer.associated-domains': config.ios.associatedDomains, + } + + fs.mkdirSync(path.dirname(entitlementsPath), { + recursive: true, + }) + fs.writeFileSync(entitlementsPath, plist.default.build(appClipEntitlements)) + + return config + }) +} + +module.exports = {withClipEntitlements} diff --git a/plugins/starterPackAppClipExtension/withClipInfoPlist.js b/plugins/starterPackAppClipExtension/withClipInfoPlist.js new file mode 100644 index 0000000000..213a53929a --- /dev/null +++ b/plugins/starterPackAppClipExtension/withClipInfoPlist.js @@ -0,0 +1,37 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withClipInfoPlist = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const targetPath = path.join( + config.modRequest.platformProjectRoot, + targetName, + 'Info.plist', + ) + + const newPlist = plist.default.build({ + NSAppClip: { + NSAppClipRequestEphemeralUserNotification: false, + NSAppClipRequestLocationConfirmation: false, + }, + UILaunchScreen: {}, + CFBundleName: '$(PRODUCT_NAME)', + CFBundleIdentifier: '$(PRODUCT_BUNDLE_IDENTIFIER)', + CFBundleVersion: '$(CURRENT_PROJECT_VERSION)', + CFBundleExecutable: '$(EXECUTABLE_NAME)', + CFBundlePackageType: '$(PRODUCT_BUNDLE_PACKAGE_TYPE)', + CFBundleShortVersionString: config.version, + UIViewControllerBasedStatusBarAppearance: 'NO', + }) + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.writeFileSync(targetPath, newPlist) + + return config + }) +} + +module.exports = {withClipInfoPlist} diff --git a/plugins/starterPackAppClipExtension/withFiles.js b/plugins/starterPackAppClipExtension/withFiles.js new file mode 100644 index 0000000000..a53a4e47aa --- /dev/null +++ b/plugins/starterPackAppClipExtension/withFiles.js @@ -0,0 +1,32 @@ +const {withXcodeProject} = require('@expo/config-plugins') +const path = require('path') +const fs = require('fs') + +const FILES = ['AppDelegate.swift', 'ViewController.swift'] + +const withFiles = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + const basePath = path.join( + config.modRequest.projectRoot, + 'modules', + targetName, + ) + + for (const file of FILES) { + const sourcePath = path.join(basePath, file) + const targetPath = path.join( + config.modRequest.platformProjectRoot, + targetName, + file, + ) + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.copyFileSync(sourcePath, targetPath) + } + + return config + }) +} + +module.exports = {withFiles} diff --git a/plugins/starterPackAppClipExtension/withStarterPackAppClip.js b/plugins/starterPackAppClipExtension/withStarterPackAppClip.js new file mode 100644 index 0000000000..1e3f0b7029 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withStarterPackAppClip.js @@ -0,0 +1,40 @@ +const {withPlugins} = require('@expo/config-plugins') +const {withAppEntitlements} = require('./withAppEntitlements') +const {withClipEntitlements} = require('./withClipEntitlements') +const {withClipInfoPlist} = require('./withClipInfoPlist') +const {withFiles} = require('./withFiles') +const {withXcodeTarget} = require('./withXcodeTarget') + +const APP_CLIP_TARGET_NAME = 'BlueskyClip' + +const withStarterPackAppClip = config => { + return withPlugins(config, [ + withAppEntitlements, + [ + withClipEntitlements, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + [ + withClipInfoPlist, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + [ + withFiles, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + [ + withXcodeTarget, + { + targetName: APP_CLIP_TARGET_NAME, + }, + ], + ]) +} + +module.exports = withStarterPackAppClip diff --git a/plugins/starterPackAppClipExtension/withXcodeTarget.js b/plugins/starterPackAppClipExtension/withXcodeTarget.js new file mode 100644 index 0000000000..a90479d4f5 --- /dev/null +++ b/plugins/starterPackAppClipExtension/withXcodeTarget.js @@ -0,0 +1,61 @@ +const {withXcodeProject} = require('@expo/config-plugins') + +const BUILD_PHASE_FILES = ['AppDelegate.swift', 'ViewController.swift'] + +const withXcodeTarget = (config, {targetName}) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + const pbxProject = config.modResults + + const target = pbxProject.addTarget(targetName, 'watch2_app', targetName) + target.pbxNativeTarget.productType = `"com.apple.product-type.application.on-demand-install-capable"` + pbxProject.addBuildPhase( + BUILD_PHASE_FILES.map(f => `${targetName}/${f}`), + 'PBXSourcesBuildPhase', + 'Sources', + target.uuid, + ) + pbxProject.addBuildPhase( + [], + 'PBXResourcesBuildPhase', + 'Resources', + target.uuid, + ) + const pbxGroupKey = pbxProject.pbxCreateGroup(targetName, targetName) + pbxProject.addFile(`${targetName}/Info.plist`, pbxGroupKey) + + const configurations = pbxProject.pbxXCBuildConfigurationSection() + for (const key in configurations) { + if (typeof configurations[key].buildSettings !== 'undefined') { + const buildSettingsObj = configurations[key].buildSettings + if ( + typeof buildSettingsObj.PRODUCT_NAME !== 'undefined' && + buildSettingsObj.PRODUCT_NAME === `"${targetName}"` + ) { + buildSettingsObj.CLANG_ENABLE_MODULES = 'YES' + buildSettingsObj.INFOPLIST_FILE = `"${targetName}/Info.plist"` + buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `"${targetName}/${targetName}.entitlements"` + buildSettingsObj.CODE_SIGN_STYLE = 'Automatic' + buildSettingsObj.CURRENT_PROJECT_VERSION = `"${ + process.env.BSKY_IOS_BUILD_NUMBER ?? '1' + }"` + buildSettingsObj.GENERATE_INFOPLIST_FILE = 'YES' + buildSettingsObj.MARKETING_VERSION = `"${config.version}"` + buildSettingsObj.PRODUCT_BUNDLE_IDENTIFIER = `"${config.ios?.bundleIdentifier}.AppClip"` + buildSettingsObj.SWIFT_EMIT_LOC_STRINGS = 'YES' + buildSettingsObj.SWIFT_VERSION = '5.0' + buildSettingsObj.TARGETED_DEVICE_FAMILY = `"1"` + buildSettingsObj.DEVELOPMENT_TEAM = 'B3LX46C5HS' + buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = '14.0' + } + } + } + + pbxProject.addTargetAttribute('DevelopmentTeam', 'B3LX46C5HS', targetName) + pbxProject.addTargetAttribute('DevelopmentTeam', 'B3LX46C5HS') + + return config + }) +} + +module.exports = {withXcodeTarget} diff --git a/scripts/updateExtensions.sh b/scripts/updateExtensions.sh index f3e972aa7b..b01134eebe 100755 --- a/scripts/updateExtensions.sh +++ b/scripts/updateExtensions.sh @@ -1,6 +1,7 @@ #!/bin/bash IOS_SHARE_EXTENSION_DIRECTORY="./ios/Share-with-Bluesky" IOS_NOTIFICATION_EXTENSION_DIRECTORY="./ios/BlueskyNSE" +IOS_APP_CLIP_DIRECTORY="./ios/BlueskyClip" MODULES_DIRECTORY="./modules" if [ ! -d $IOS_SHARE_EXTENSION_DIRECTORY ]; then @@ -16,3 +17,11 @@ if [ ! -d $IOS_NOTIFICATION_EXTENSION_DIRECTORY ]; then else cp -R $IOS_NOTIFICATION_EXTENSION_DIRECTORY $MODULES_DIRECTORY fi + + +if [ ! -d $IOS_APP_CLIP_DIRECTORY ]; then + echo "$IOS_APP_CLIP_DIRECTORY not found inside of your iOS project." + exit 1 +else + cp -R $IOS_APP_CLIP_DIRECTORY $MODULES_DIRECTORY +fi diff --git a/src/components/StarterPack/NewskieDialog.tsx b/src/components/StarterPack/NewskieDialog.tsx index c4202a4fc2..ca3516cb92 100644 --- a/src/components/StarterPack/NewskieDialog.tsx +++ b/src/components/StarterPack/NewskieDialog.tsx @@ -26,6 +26,7 @@ export function NewskieDialog({ const control = useDialogControl() const {joinedViaStarterPack} = profile const profileName = profile.displayName || `@${profile.handle}` + const createdAt = profile.createdAt ?? 0 return ( <> @@ -52,8 +53,7 @@ export function NewskieDialog({ joinedViaStarterPack?.record, ) ? ( - {profileName} joined Bluesky {ago(profile.createdAt, true)}{' '} - ago with{' '} + {profileName} joined Bluesky {ago(createdAt, true)} ago with{' '} {joinedViaStarterPack?.creator.did === currentAccount?.did ? 'your' : `${joinedViaStarterPack?.creator.displayName}'s` || @@ -63,7 +63,7 @@ export function NewskieDialog({ ) : ( {profileName}{' '} - recently joined Bluesky {ago(profile.createdAt, true)} ago + recently joined Bluesky {ago(createdAt, true)} ago )} diff --git a/src/components/hooks/useStarterPackEntry.ts b/src/components/hooks/useStarterPackEntry.ts index dc92a0b698..51242a95ec 100644 --- a/src/components/hooks/useStarterPackEntry.ts +++ b/src/components/hooks/useStarterPackEntry.ts @@ -9,10 +9,12 @@ export function useStarterPackEntry() { const url = new URL(window.location.href) if (url.pathname.startsWith('/start/')) { const [_, _start, name, rkey] = url.pathname.split('/') + const isClip = url.searchParams.get('clip') === 'true' if (name && rkey) { setUsedStarterPack({ uri: window.location.href, + isClip, }) } } diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index 3a520f1605..557daa68b0 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -20,19 +20,12 @@ export function ProfileHeaderHandle({ const t = useTheme() const invalidHandle = isInvalidHandle(profile.handle) const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy - const createdAt = new Date(profile.createdAt).getTime() + const createdAt = profile.createdAt + ? new Date(profile.createdAt).getTime() + : 0 const isNewskie = - createdAt > 0 && - Date.now() + 60e3 * 24 * DAYS_TO_SHOW_NEWSKIE > - new Date(profile.createdAt).getTime() - - console.log({ - date: Date.now() + 60e3 * 24 * DAYS_TO_SHOW_NEWSKIE, - createdAt: new Date(profile.createdAt).getTime(), - }) - - console.log(isNewskie) + createdAt > 0 && Date.now() + 60e3 * 24 * DAYS_TO_SHOW_NEWSKIE > createdAt return ( item.subject) @@ -119,6 +120,9 @@ function LandingScreenInner({ borderBottomLeftRadius: 10, borderBottomRightRadius: 10, }, + usedStarterPack?.isClip && { + paddingTop: 100, + }, ]}> diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 19304b5cbe..06c10b2ae0 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -93,6 +93,7 @@ export const schema = z.object({ uri: z.string(), cid: z.string().optional(), initialFeed: z.string().optional(), + isClip: z.boolean().optional(), }) .optional(), }) diff --git a/src/state/preferences/starter-pack.tsx b/src/state/preferences/starter-pack.tsx index 703080d7c0..48fd4298bc 100644 --- a/src/state/preferences/starter-pack.tsx +++ b/src/state/preferences/starter-pack.tsx @@ -3,7 +3,7 @@ import React from 'react' import * as persisted from '#/state/persisted' type StateContext = - | {uri: string; cid?: string; initialFeed?: string} + | {uri: string; cid?: string; initialFeed?: string; isClip?: boolean} | undefined type SetContext = (v: StateContext) => void diff --git a/yarn.lock b/yarn.lock index 9ac8e622cc..d1299545be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3403,27 +3403,6 @@ xcode "^3.0.1" xml2js "0.6.0" -"@expo/config-plugins@~7.2.5": - version "7.2.5" - resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.2.5.tgz#b15f22878975fdc4ddcfa8cdc971937ddc4c0249" - integrity sha512-w+5ccu1IxBHgyQk9CPFKLZOk8yZQEyTjbJwOzESK1eR7QwosbcsLkN1c1WWUZYiCXwORu3UTwJYll4+X2xxJhQ== - dependencies: - "@expo/config-types" "^49.0.0-alpha.1" - "@expo/json-file" "~8.2.37" - "@expo/plist" "^0.0.20" - "@expo/sdk-runtime-versions" "^1.0.0" - "@react-native/normalize-color" "^2.0.0" - chalk "^4.1.2" - debug "^4.3.1" - find-up "~5.0.0" - getenv "^1.0.0" - glob "7.1.6" - resolve-from "^5.0.0" - semver "^7.5.3" - slash "^3.0.0" - xcode "^3.0.1" - xml2js "0.6.0" - "@expo/config-plugins@~7.8.0": version "7.8.0" resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.8.0.tgz#70fd87237faf6a5c3bf47277b67f7b22f9b12c05" @@ -3446,11 +3425,6 @@ xcode "^3.0.1" xml2js "0.6.0" -"@expo/config-types@^49.0.0-alpha.1": - version "49.0.0" - resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-49.0.0.tgz#15ffef715285c06703f6fb7ec0cda853f645cc09" - integrity sha512-8eyREVi+K2acnMBe/rTIu1dOfyR2+AMnTLHlut+YpMV9OZPdeKV0Bs9BxAewGqBA2slslbQ9N39IS2CuTKpXkA== - "@expo/config-types@^50.0.0-alpha.1": version "50.0.0" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-50.0.0.tgz#b534d3ec997ec60f8af24f6ad56244c8afc71a0b" @@ -3624,7 +3598,7 @@ semver "^7.6.0" tempy "0.3.0" -"@expo/json-file@^8.2.37", "@expo/json-file@~8.2.37": +"@expo/json-file@^8.2.37": version "8.2.37" resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.37.tgz#9c02d3b42134907c69cc0a027b18671b69344049" integrity sha512-YaH6rVg11JoTS2P6LsW7ybS2CULjf40AbnAHw2F1eDPuheprNjARZMnyHFPkKv7GuxCy+B9GPcbOKgc4cgA80Q== @@ -3725,15 +3699,6 @@ split "^1.0.1" sudo-prompt "9.1.1" -"@expo/plist@^0.0.20": - version "0.0.20" - resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.20.tgz#a6b3124438031c02b762bad5a47b70584d3c0072" - integrity sha512-UXQ4LXCfTZ580LDHGJ5q62jSTwJFFJ1GqBu8duQMThiHKWbMJ+gajJh6rsB6EJ3aLUr9wcauxneL5LVRFxwBEA== - dependencies: - "@xmldom/xmldom" "~0.7.7" - base64-js "^1.2.3" - xmlbuilder "^14.0.0" - "@expo/plist@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.1.0.tgz#eabc95f951d14e10c87fd0443ee01d567371f058" @@ -18823,13 +18788,6 @@ react-keyed-flatten-children@^3.0.0: dependencies: react-is "^18.2.0" -react-native-app-clip@bluesky-social/react-native-app-clip: - version "0.3.0" - resolved "https://codeload.github.com/bluesky-social/react-native-app-clip/tar.gz/05d57c10c5d9799a6bb83d5ee7d1960145c60146" - dependencies: - "@expo/config-plugins" "~7.2.5" - "@expo/plist" "^0.0.20" - react-native-date-picker@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/react-native-date-picker/-/react-native-date-picker-4.4.2.tgz#f7bb9daa8559237e08bd30f907ee8487a6e2a6ec"