From d69f197fbc803bbb001f08b931128b10648ca6d6 Mon Sep 17 00:00:00 2001 From: Gabriel Donadel Dall'Agnol Date: Tue, 19 Sep 2023 12:29:59 -0300 Subject: [PATCH] [menu-bar] Add auto update support (#65) * [menu-bar] Add auto update support * Add appcast file * Update Settings window * Add changelog entry --- CHANGELOG.md | 3 +- appcast.xml | 40 +++++++++++++ .../macos/ExpoMenuBar-macOS/Info.plist | 2 + .../macos/ExpoMenuBar-macOS/SparkleModule.h | 4 ++ .../macos/ExpoMenuBar-macOS/SparkleModule.m | 46 +++++++++++++++ .../ExpoMenuBar.xcodeproj/project.pbxproj | 18 +++++- apps/menu-bar/macos/Podfile | 1 + apps/menu-bar/macos/Podfile.lock | 6 +- apps/menu-bar/src/components/Checkbox.tsx | 45 ++++++--------- apps/menu-bar/src/modules/SparkleModule.ts | 15 +++++ apps/menu-bar/src/windows/Settings.tsx | 57 +++++++++++++------ 11 files changed, 185 insertions(+), 52 deletions(-) create mode 100644 appcast.xml create mode 100644 apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.h create mode 100644 apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.m create mode 100644 apps/menu-bar/src/modules/SparkleModule.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c7e21e..163f2c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,9 @@ ## 0.1.3 — 2023-09 -### 🛠 Breaking changes - ### 🎉 New features +- Add auto update support. ([#65](https://github.com/expo/orbit/pull/65) by [@gabrieldonadel](https://github.com/gabrieldonadel)) - Improved performance when running `cli` commands. ([#61](https://github.com/expo/orbit/pull/61) by [@gabrieldonadel](https://github.com/gabrieldonadel)) - Show dock icon while windows are opened. ([#50](https://github.com/expo/orbit/pull/50) by [@gabrieldonadel](https://github.com/gabrieldonadel)) diff --git a/appcast.xml b/appcast.xml new file mode 100644 index 00000000..19126502 --- /dev/null +++ b/appcast.xml @@ -0,0 +1,40 @@ + + + + Orbit + https://raw.githubusercontent.com/expo/orbit/main/appcast.xml + Most recent changes with links to updates. + en + + 0.1.2 + + 🎉 New features +
    +
  • Added a context menu when right clicking on the menu bar icon. (#36 by @gabrieldonadel)
  • +
+

🐛 Bug fixes

+ +

💡 Others

+ + ]]> +
+ 2023-08-13 + https://github.com/expo/orbit/releases/tag/expo-orbit-v0.1.2 + 10.15 + +
+
+
diff --git a/apps/menu-bar/macos/ExpoMenuBar-macOS/Info.plist b/apps/menu-bar/macos/ExpoMenuBar-macOS/Info.plist index a4a5b96f..c4038ce6 100644 --- a/apps/menu-bar/macos/ExpoMenuBar-macOS/Info.plist +++ b/apps/menu-bar/macos/ExpoMenuBar-macOS/Info.plist @@ -57,5 +57,7 @@ NSSupportsSuddenTermination + SUFeedURL + https://raw.githubusercontent.com/expo/orbit/main/appcast.xml diff --git a/apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.h b/apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.h new file mode 100644 index 00000000..34111700 --- /dev/null +++ b/apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.h @@ -0,0 +1,4 @@ +#import + +@interface SparkleModule : NSObject +@end diff --git a/apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.m b/apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.m new file mode 100644 index 00000000..b44ad87f --- /dev/null +++ b/apps/menu-bar/macos/ExpoMenuBar-macOS/SparkleModule.m @@ -0,0 +1,46 @@ +#import "SparkleModule.h" +#import "AppDelegate.h" + +#import +#import + +@implementation SparkleModule { + SPUStandardUpdaterController *updateController; +} + +RCT_EXPORT_MODULE(); + +- (instancetype)init { + self = [super init]; + if (self) { + updateController = [[SPUStandardUpdaterController alloc] initWithStartingUpdater:YES updaterDelegate:nil userDriverDelegate:nil]; + } + return self; +} + + ++ (BOOL)requiresMainQueueSetup +{ + return YES; +} + + +RCT_EXPORT_METHOD(checkForUpdates) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [[self->updateController updater] checkForUpdates]; + }); +} + +RCT_EXPORT_METHOD(getAutomaticallyChecksForUpdates:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + resolve(@([[self->updateController updater] automaticallyChecksForUpdates])); +} + +RCT_EXPORT_METHOD(setAutomaticallyChecksForUpdates:(BOOL)value) +{ + [[self->updateController updater] setAutomaticallyChecksForUpdates:value]; +} + +@end diff --git a/apps/menu-bar/macos/ExpoMenuBar.xcodeproj/project.pbxproj b/apps/menu-bar/macos/ExpoMenuBar.xcodeproj/project.pbxproj index f29eb000..f44fb51e 100644 --- a/apps/menu-bar/macos/ExpoMenuBar.xcodeproj/project.pbxproj +++ b/apps/menu-bar/macos/ExpoMenuBar.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ C08E65342A5D04910079E3A9 /* WindowNavigator.m in Sources */ = {isa = PBXBuildFile; fileRef = C08E65332A5D04910079E3A9 /* WindowNavigator.m */; }; C0B36EA22A65E25A004F2D8C /* Checkbox.m in Sources */ = {isa = PBXBuildFile; fileRef = C0B36E9F2A65E25A004F2D8C /* Checkbox.m */; }; C0B36EA32A65E25A004F2D8C /* CheckboxManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C0B36EA12A65E25A004F2D8C /* CheckboxManager.m */; }; + C0C820022AB8E5EE003D75AF /* SparkleModule.m in Sources */ = {isa = PBXBuildFile; fileRef = C0C820012AB8E5EE003D75AF /* SparkleModule.m */; }; C0E7CF482A15378D00C59DE5 /* MenuBarModule.m in Sources */ = {isa = PBXBuildFile; fileRef = C0E7CF472A15378D00C59DE5 /* MenuBarModule.m */; }; E1CB5365F6C6CCE90A6EF541 /* libPods-ExpoMenuBar-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C6FA792906717DFD0B3546C1 /* libPods-ExpoMenuBar-macOS.a */; }; /* End PBXBuildFile section */ @@ -97,6 +98,8 @@ C0B36E9F2A65E25A004F2D8C /* Checkbox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Checkbox.m; sourceTree = ""; }; C0B36EA02A65E25A004F2D8C /* CheckboxManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CheckboxManager.h; sourceTree = ""; }; C0B36EA12A65E25A004F2D8C /* CheckboxManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CheckboxManager.m; sourceTree = ""; }; + C0C820012AB8E5EE003D75AF /* SparkleModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SparkleModule.m; sourceTree = ""; }; + C0C820032AB8E600003D75AF /* SparkleModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SparkleModule.h; sourceTree = ""; }; C0E7CF462A15372D00C59DE5 /* MenuBarModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MenuBarModule.h; sourceTree = ""; }; C0E7CF472A15378D00C59DE5 /* MenuBarModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MenuBarModule.m; sourceTree = ""; }; C6FA792906717DFD0B3546C1 /* libPods-ExpoMenuBar-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ExpoMenuBar-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -191,6 +194,8 @@ C0271C572A602CDE0065AB11 /* ProgressIndicatorManager.m */, C0271C592A602FEC0065AB11 /* ProgressIndicatorView.h */, C0271C5A2A602FF60065AB11 /* ProgressIndicatorView.m */, + C0C820012AB8E5EE003D75AF /* SparkleModule.m */, + C0C820032AB8E600003D75AF /* SparkleModule.h */, ); path = "ExpoMenuBar-macOS"; sourceTree = ""; @@ -432,10 +437,12 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-ExpoMenuBar-macOS/Pods-ExpoMenuBar-macOS-frameworks.sh", + "${PODS_ROOT}/Sparkle/Sparkle.framework", "${PODS_ROOT}/hermes-engine/destroot/Library/Frameworks/macosx/hermes.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -455,6 +462,7 @@ C0523ED02A55980D003371AF /* WindowWithDeallocCallback.m in Sources */, C0271C582A602CDE0065AB11 /* ProgressIndicatorManager.m in Sources */, C0B36EA32A65E25A004F2D8C /* CheckboxManager.m in Sources */, + C0C820022AB8E5EE003D75AF /* SparkleModule.m in Sources */, C0E7CF482A15378D00C59DE5 /* MenuBarModule.m in Sources */, C0271C5B2A602FF60065AB11 /* ProgressIndicatorView.m in Sources */, C0523ECC2A550983003371AF /* WindowManager.m in Sources */, @@ -504,7 +512,10 @@ INFOPLIST_KEY_CFBundleDisplayName = "Expo Orbit"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; - OTHER_CFLAGS = "$(inherited) "; + OTHER_CFLAGS = ( + "$(inherited)", + " ", + ); OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -532,7 +543,10 @@ INFOPLIST_KEY_CFBundleDisplayName = "Expo Orbit"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; - OTHER_CFLAGS = "$(inherited) "; + OTHER_CFLAGS = ( + "$(inherited)", + " ", + ); OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/apps/menu-bar/macos/Podfile b/apps/menu-bar/macos/Podfile index 90659153..3f6fd63e 100644 --- a/apps/menu-bar/macos/Podfile +++ b/apps/menu-bar/macos/Podfile @@ -27,6 +27,7 @@ target 'ExpoMenuBar-macOS' do ) # Pods specifically for macOS target + pod 'Sparkle', '~> 2.5.0' end post_install do |installer| diff --git a/apps/menu-bar/macos/Podfile.lock b/apps/menu-bar/macos/Podfile.lock index 4c99e8da..bb188bb6 100644 --- a/apps/menu-bar/macos/Podfile.lock +++ b/apps/menu-bar/macos/Podfile.lock @@ -372,6 +372,7 @@ PODS: - RNSVG (13.9.0): - React-Core - SocketRocket (0.6.0) + - Sparkle (2.5.0) - Yoga (1.14.0) DEPENDENCIES: @@ -413,6 +414,7 @@ DEPENDENCIES: - "RNCAsyncStorage (from `../../../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../../../node_modules/@react-native-clipboard/clipboard`)" - RNSVG (from `../../../node_modules/react-native-svg`) + - Sparkle (~> 2.5.0) - Yoga (from `../../../node_modules/react-native-macos/ReactCommon/yoga`) SPEC REPOS: @@ -420,6 +422,7 @@ SPEC REPOS: - fmt - libevent - SocketRocket + - Sparkle EXTERNAL SOURCES: boost: @@ -537,8 +540,9 @@ SPEC CHECKSUMS: RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 + Sparkle: 913cef92f73e08d0ae47a36ee20e523913e5f9e2 Yoga: 4e076ec2978a0800817878be120de7e97b79bcf3 -PODFILE CHECKSUM: b107cc642a2952bfc0a576b4722750483387b7f7 +PODFILE CHECKSUM: 86cfebf9b1835c1a1feb1306c01bea9e04d5758d COCOAPODS: 1.12.1 diff --git a/apps/menu-bar/src/components/Checkbox.tsx b/apps/menu-bar/src/components/Checkbox.tsx index d05dd0a6..20a74906 100644 --- a/apps/menu-bar/src/components/Checkbox.tsx +++ b/apps/menu-bar/src/components/Checkbox.tsx @@ -1,4 +1,4 @@ -import {useLayoutEffect, useRef, useState} from 'react'; +import { useLayoutEffect, useRef, useState } from 'react'; import { NativeSyntheticEvent, Pressable, @@ -8,14 +8,15 @@ import { ViewProps, } from 'react-native'; import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands'; -import {Text} from './Text'; + +import { Text } from './Text'; +import { Row } from './View'; interface CheckboxChangeEventData extends TargetedEvent { value: boolean; } -interface CheckboxChangeEvent - extends NativeSyntheticEvent {} +interface CheckboxChangeEvent extends NativeSyntheticEvent {} type NativeCheckboxProps = ViewProps & { disabled?: boolean; @@ -31,39 +32,25 @@ type CheckboxProps = NativeCheckboxProps & { }; interface NativeCommands { - setValue: ( - viewRef: React.ElementRef, - value: boolean, - ) => void; + setValue: (viewRef: React.ElementRef, value: boolean) => void; } export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: ['setValue'], }); -const Checkbox = ({ - onChange, - onValueChange, - label, - ...props -}: CheckboxProps) => { - const [native, setNative] = useState<{value?: boolean}>({value: undefined}); +const Checkbox = ({ onChange, onValueChange, label, ...props }: CheckboxProps) => { + const [native, setNative] = useState<{ value?: boolean }>({ value: undefined }); - const nativeSwitchRef = useRef | null>(null); + const nativeSwitchRef = useRef | null>(null); useLayoutEffect(() => { // This is necessary in case native updates the switch and JS decides // that the update should be ignored and we should stick with the value // that we have in JS. const jsValue = props.value === true; - const shouldUpdateNativeSwitch = - native.value != null && native.value !== jsValue; - if ( - shouldUpdateNativeSwitch && - nativeSwitchRef.current?.setNativeProps != null - ) { + const shouldUpdateNativeSwitch = native.value != null && native.value !== jsValue; + if (shouldUpdateNativeSwitch && nativeSwitchRef.current?.setNativeProps != null) { Commands.setValue(nativeSwitchRef.current, jsValue); } }, [props.value, native]); @@ -71,11 +58,11 @@ const Checkbox = ({ const handleChange = (event: CheckboxChangeEvent) => { onChange?.(event); onValueChange?.(event.nativeEvent.value); - setNative({value: event.nativeEvent.value}); + setNative({ value: event.nativeEvent.value }); }; return ( - <> + { onValueChange?.(!props.value); - setNative({value: !props.value}); + setNative({ value: !props.value }); }}> {label} )} - + ); }; export default Checkbox; const styles = StyleSheet.create({ - checkbox: {height: 18, width: 18}, + checkbox: { height: 18, width: 18 }, }); diff --git a/apps/menu-bar/src/modules/SparkleModule.ts b/apps/menu-bar/src/modules/SparkleModule.ts new file mode 100644 index 00000000..6e1e1305 --- /dev/null +++ b/apps/menu-bar/src/modules/SparkleModule.ts @@ -0,0 +1,15 @@ +import { NativeModule, NativeModules } from 'react-native'; + +type SparkleModuleType = NativeModule & { + checkForUpdates: () => void; + getAutomaticallyChecksForUpdates: () => Promise; + setAutomaticallyChecksForUpdates: (value: boolean) => void; +}; + +const SparkleModule: SparkleModuleType = NativeModules.SparkleModule; + +export default { + ...SparkleModule, + checkForUpdates: () => SparkleModule.checkForUpdates(), + getAutomaticallyChecksForUpdates: () => SparkleModule.getAutomaticallyChecksForUpdates(), +}; diff --git a/apps/menu-bar/src/windows/Settings.tsx b/apps/menu-bar/src/windows/Settings.tsx index 4ad131de..198f7555 100644 --- a/apps/menu-bar/src/windows/Settings.tsx +++ b/apps/menu-bar/src/windows/Settings.tsx @@ -1,9 +1,11 @@ -import {useEffect, useState} from 'react'; -import {Alert, StyleSheet, TouchableOpacity} from 'react-native'; +import { useEffect, useState } from 'react'; +import { Alert, StyleSheet, TouchableOpacity } from 'react-native'; import MenuBarModule from '../modules/MenuBarModule'; -import {Checkbox, View, Row, Text, Divider} from '../components'; +import { Checkbox, View, Row, Text, Divider } from '../components'; +import Button from '../components/Button'; import PathInput from '../components/PathInput'; +import SparkleModule from '../modules/SparkleModule'; import { UserPreferences, getUserPreferences, @@ -14,19 +16,21 @@ import { const Settings = () => { const [userPreferences, setUserPreferences] = useState({}); const [customSdkPathEnabled, setCustomSdkPathEnabled] = useState(false); + const [automaticallyChecksForUpdates, setAutomaticallyChecksForUpdates] = useState(false); useEffect(() => { - getUserPreferences().then(value => { + getUserPreferences().then((value) => { setUserPreferences(value); setCustomSdkPathEnabled(Boolean(value.customSdkPath)); }); + SparkleModule.getAutomaticallyChecksForUpdates().then(setAutomaticallyChecksForUpdates); }, []); const onPressLaunchOnLogin = async (value: boolean) => { try { await MenuBarModule.setLoginItemEnabled(value); - setUserPreferences(prev => { - const newPreferences = {...prev, launchOnLogin: value}; + setUserPreferences((prev) => { + const newPreferences = { ...prev, launchOnLogin: value }; saveUserPreferences(newPreferences); return newPreferences; }); @@ -40,16 +44,21 @@ const Settings = () => { text: 'Open Settings', onPress: MenuBarModule.openSystemSettingsLoginItems, }, - {text: 'Cancel', style: 'cancel'}, - ], + { text: 'Cancel', style: 'cancel' }, + ] ); } } }; + const onPressSetAutomaticallyChecksForUpdates = async (value: boolean) => { + setAutomaticallyChecksForUpdates(value); + SparkleModule.setAutomaticallyChecksForUpdates(value); + }; + const onPressEmulatorWithoutAudio = async (value: boolean) => { - setUserPreferences(prev => { - const newPreferences = {...prev, emulatorWithoutAudio: value}; + setUserPreferences((prev) => { + const newPreferences = { ...prev, emulatorWithoutAudio: value }; saveUserPreferences(newPreferences); return newPreferences; }); @@ -58,8 +67,8 @@ const Settings = () => { const toggleCustomSdkPath = (value: boolean) => { setCustomSdkPathEnabled(value); if (!value) { - setUserPreferences(prev => { - const newPreferences = {...prev, customSdkPath: undefined}; + setUserPreferences((prev) => { + const newPreferences = { ...prev, customSdkPath: undefined }; saveUserPreferences(newPreferences); MenuBarModule.setEnvVars({}); return newPreferences; @@ -70,21 +79,33 @@ const Settings = () => { return ( - + - + + +