Updated the Sample app and Widget targets in the Xcode project to make the app works for visionOS.
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
02870A292FF7FF530079EA3A /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02870A282FF7FF530079EA3A /* Features */; };
|
02870A292FF7FF530079EA3A /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02870A282FF7FF530079EA3A /* Features */; };
|
||||||
0296F7F02FFAB1B600D2C5FC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7EF2FFAB1B600D2C5FC /* WidgetKit.framework */; };
|
0296F7F02FFAB1B600D2C5FC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7EF2FFAB1B600D2C5FC /* WidgetKit.framework */; };
|
||||||
0296F7F22FFAB1B600D2C5FC /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7F12FFAB1B600D2C5FC /* SwiftUI.framework */; };
|
0296F7F22FFAB1B600D2C5FC /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7F12FFAB1B600D2C5FC /* SwiftUI.framework */; };
|
||||||
0296F7FF2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0296F7EE2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex */; platformFilters = (ios, xros, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
0296F7FF2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0296F7EE2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
02D46AFE2FFAE77500266643 /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02D46AFD2FFAE77500266643 /* Features */; };
|
02D46AFE2FFAE77500266643 /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02D46AFD2FFAE77500266643 /* Features */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -342,10 +342,7 @@
|
|||||||
};
|
};
|
||||||
0296F7FE2FFAB1B600D2C5FC /* PBXTargetDependency */ = {
|
0296F7FE2FFAB1B600D2C5FC /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
platformFilters = (
|
platformFilter = ios;
|
||||||
ios,
|
|
||||||
xros,
|
|
||||||
);
|
|
||||||
target = 0296F7ED2FFAB1B600D2C5FC /* AttendiWidgetExtension */;
|
target = 0296F7ED2FFAB1B600D2C5FC /* AttendiWidgetExtension */;
|
||||||
targetProxy = 0296F7FD2FFAB1B600D2C5FC /* PBXContainerItemProxy */;
|
targetProxy = 0296F7FD2FFAB1B600D2C5FC /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
@@ -833,7 +830,7 @@
|
|||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@@ -841,7 +838,7 @@
|
|||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,4,7";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 26.0;
|
WATCHOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
@@ -925,14 +922,14 @@
|
|||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,4,7";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 26.0;
|
WATCHOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,10 +45,14 @@ public struct NotificationLabelStyle: LabelStyle {
|
|||||||
}
|
}
|
||||||
.padding(.vertical, Constant.Padding.vertical)
|
.padding(.vertical, Constant.Padding.vertical)
|
||||||
.padding(.horizontal, Constant.Padding.horizontal)
|
.padding(.horizontal, Constant.Padding.horizontal)
|
||||||
|
#if os(visionOS)
|
||||||
|
.glassBackgroundEffect(in: .capsule)
|
||||||
|
#else
|
||||||
.glassEffect(
|
.glassEffect(
|
||||||
.regular,
|
.regular,
|
||||||
in: .capsule
|
in: .capsule
|
||||||
)
|
)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import SwiftUI
|
|||||||
/// The button style for the controls of a ``RecordingView``.
|
/// The button style for the controls of a ``RecordingView``.
|
||||||
///
|
///
|
||||||
/// The style renders the button's label in the primary foreground color over a padded, circular Liquid Glass background — tinted by
|
/// The style renders the button's label in the primary foreground color over a padded, circular Liquid Glass background — tinted by
|
||||||
/// the color given at initialization — that reacts fluidly to presses. The whole button dims while disabled, and both the label and its
|
/// the color given at initialization — that reacts fluidly to presses. On visionOS, where Liquid Glass is unavailable, the background is
|
||||||
|
/// the platform's own glass material overlaid with the tint color. The whole button dims while disabled, and both the label and its
|
||||||
/// padding scale relative to the current dynamic type size.
|
/// padding scale relative to the current dynamic type size.
|
||||||
struct RecordingButtonStyle: ButtonStyle {
|
struct RecordingButtonStyle: ButtonStyle {
|
||||||
|
|
||||||
@@ -52,6 +53,14 @@ struct RecordingButtonStyle: ButtonStyle {
|
|||||||
.padding(padding)
|
.padding(padding)
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.foregroundStyle(.primary.opacity(opacity))
|
.foregroundStyle(.primary.opacity(opacity))
|
||||||
|
#if os(visionOS)
|
||||||
|
.background(
|
||||||
|
tintColor.opacity(Constant.Opacity.tint),
|
||||||
|
in: .circle
|
||||||
|
)
|
||||||
|
.glassBackgroundEffect(in: .circle)
|
||||||
|
.opacity(opacity)
|
||||||
|
#else
|
||||||
.glassEffect(
|
.glassEffect(
|
||||||
.regular
|
.regular
|
||||||
.tint(tintColor)
|
.tint(tintColor)
|
||||||
@@ -59,6 +68,7 @@ struct RecordingButtonStyle: ButtonStyle {
|
|||||||
in: .circle
|
in: .circle
|
||||||
)
|
)
|
||||||
.opacity(opacity)
|
.opacity(opacity)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -108,6 +118,8 @@ private enum Constant {
|
|||||||
static let disabled: Double = 0.5
|
static let disabled: Double = 0.5
|
||||||
/// The opacity of an enabled button.
|
/// The opacity of an enabled button.
|
||||||
static let enabled: Double = 1
|
static let enabled: Double = 1
|
||||||
|
/// The opacity of the tint color laid over the glass background on visionOS.
|
||||||
|
static let tint: Double = 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The padding constants.
|
/// The padding constants.
|
||||||
|
|||||||
@@ -81,7 +81,66 @@ public struct RecordingView: View {
|
|||||||
.accessibilityValue(model.textTimerAccessible)
|
.accessibilityValue(model.textTimerAccessible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(visionOS)
|
||||||
|
controls
|
||||||
|
#else
|
||||||
GlassEffectContainer {
|
GlassEffectContainer {
|
||||||
|
controls
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.animation(
|
||||||
|
.easeInOut,
|
||||||
|
value: model.state
|
||||||
|
)
|
||||||
|
.onChange(
|
||||||
|
of: model.transcription,
|
||||||
|
initial: false
|
||||||
|
) { _, newValue in
|
||||||
|
if let newValue {
|
||||||
|
onTranscription(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert(
|
||||||
|
.viewRecordingAlertErrorTitle,
|
||||||
|
isPresented: .init {
|
||||||
|
model.error != nil
|
||||||
|
} set: { isPresented in
|
||||||
|
if !isPresented {
|
||||||
|
model.dismissedError()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
presenting: model.error
|
||||||
|
) { error in
|
||||||
|
if error == .permissionDenied, let url = URL.settings {
|
||||||
|
Button(.viewRecordingAlertErrorButtonSettings) {
|
||||||
|
openURL(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
.viewRecordingAlertErrorButtonOk,
|
||||||
|
role: .cancel
|
||||||
|
) {
|
||||||
|
// Dismissal is handled by the presentation binding.
|
||||||
|
}
|
||||||
|
} message: { _ in
|
||||||
|
if let message = model.textAlertMessage {
|
||||||
|
Text(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Subviews
|
||||||
|
|
||||||
|
private extension RecordingView {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
/// The main, discard, and send buttons controlling the recording flow, laid out in a horizontal stack.
|
||||||
|
var controls: some View {
|
||||||
HStack(
|
HStack(
|
||||||
spacing: Constant.Spacing.stack
|
spacing: Constant.Spacing.stack
|
||||||
) {
|
) {
|
||||||
@@ -129,48 +188,6 @@ public struct RecordingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.animation(
|
|
||||||
.easeInOut,
|
|
||||||
value: model.state
|
|
||||||
)
|
|
||||||
.onChange(
|
|
||||||
of: model.transcription,
|
|
||||||
initial: false
|
|
||||||
) { _, newValue in
|
|
||||||
if let newValue {
|
|
||||||
onTranscription(newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.alert(
|
|
||||||
.viewRecordingAlertErrorTitle,
|
|
||||||
isPresented: .init {
|
|
||||||
model.error != nil
|
|
||||||
} set: { isPresented in
|
|
||||||
if !isPresented {
|
|
||||||
model.dismissedError()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
presenting: model.error
|
|
||||||
) { error in
|
|
||||||
if error == .permissionDenied, let url = URL.settings {
|
|
||||||
Button(.viewRecordingAlertErrorButtonSettings) {
|
|
||||||
openURL(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
.viewRecordingAlertErrorButtonOk,
|
|
||||||
role: .cancel
|
|
||||||
) {
|
|
||||||
// Dismissal is handled by the presentation binding.
|
|
||||||
}
|
|
||||||
} message: { _ in
|
|
||||||
if let message = model.textAlertMessage {
|
|
||||||
Text(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user