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 */; };
|
||||
0296F7F02FFAB1B600D2C5FC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7EF2FFAB1B600D2C5FC /* WidgetKit.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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -342,10 +342,7 @@
|
||||
};
|
||||
0296F7FE2FFAB1B600D2C5FC /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilters = (
|
||||
ios,
|
||||
xros,
|
||||
);
|
||||
platformFilter = ios;
|
||||
target = 0296F7ED2FFAB1B600D2C5FC /* AttendiWidgetExtension */;
|
||||
targetProxy = 0296F7FD2FFAB1B600D2C5FC /* PBXContainerItemProxy */;
|
||||
};
|
||||
@@ -833,7 +830,7 @@
|
||||
SDKROOT = auto;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@@ -841,7 +838,7 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,4,7";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 26.0;
|
||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||
};
|
||||
@@ -925,14 +922,14 @@
|
||||
SDKROOT = auto;
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,4,7";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 26.0;
|
||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||
};
|
||||
|
||||
@@ -45,10 +45,14 @@ public struct NotificationLabelStyle: LabelStyle {
|
||||
}
|
||||
.padding(.vertical, Constant.Padding.vertical)
|
||||
.padding(.horizontal, Constant.Padding.horizontal)
|
||||
#if os(visionOS)
|
||||
.glassBackgroundEffect(in: .capsule)
|
||||
#else
|
||||
.glassEffect(
|
||||
.regular,
|
||||
in: .capsule
|
||||
)
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import SwiftUI
|
||||
/// 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 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.
|
||||
struct RecordingButtonStyle: ButtonStyle {
|
||||
|
||||
@@ -52,6 +53,14 @@ struct RecordingButtonStyle: ButtonStyle {
|
||||
.padding(padding)
|
||||
.scaledToFit()
|
||||
.foregroundStyle(.primary.opacity(opacity))
|
||||
#if os(visionOS)
|
||||
.background(
|
||||
tintColor.opacity(Constant.Opacity.tint),
|
||||
in: .circle
|
||||
)
|
||||
.glassBackgroundEffect(in: .circle)
|
||||
.opacity(opacity)
|
||||
#else
|
||||
.glassEffect(
|
||||
.regular
|
||||
.tint(tintColor)
|
||||
@@ -59,6 +68,7 @@ struct RecordingButtonStyle: ButtonStyle {
|
||||
in: .circle
|
||||
)
|
||||
.opacity(opacity)
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -108,6 +118,8 @@ private enum Constant {
|
||||
static let disabled: Double = 0.5
|
||||
/// The opacity of an enabled button.
|
||||
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.
|
||||
|
||||
@@ -81,7 +81,66 @@ public struct RecordingView: View {
|
||||
.accessibilityValue(model.textTimerAccessible)
|
||||
}
|
||||
|
||||
#if os(visionOS)
|
||||
controls
|
||||
#else
|
||||
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(
|
||||
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