✨ Circular accessory on iOS
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 77;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -216,6 +216,8 @@
|
|||||||
};
|
};
|
||||||
7310A7D52EB10962002C0FD3 /* Solian Watch App */ = {
|
7310A7D52EB10962002C0FD3 /* Solian Watch App */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = "Solian Watch App";
|
path = "Solian Watch App";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -757,14 +759,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -822,14 +820,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@@ -880,14 +874,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks.sh\"\n";
|
||||||
|
|||||||
@@ -276,6 +276,18 @@ struct NotificationWidgetEntryView: View {
|
|||||||
if case .accessoryRectangular = family {
|
if case .accessoryRectangular = family {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if case .accessoryCircular = family {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isCircular: Bool {
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
if case .accessoryCircular = family {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -285,40 +297,57 @@ struct NotificationWidgetEntryView: View {
|
|||||||
Link(destination: URL(string: "solian://notifications")!) {
|
Link(destination: URL(string: "solian://notifications")!) {
|
||||||
if isCompact {
|
if isCompact {
|
||||||
if isAccessory {
|
if isAccessory {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
if isCircular {
|
||||||
HStack(spacing: 4) {
|
ZStack {
|
||||||
Image(systemName: "bell.fill")
|
Image(systemName: "bell.fill")
|
||||||
.font(.caption2)
|
.font(.system(size: 20))
|
||||||
.foregroundColor(.orange)
|
.foregroundColor(unreadCount > 0 ? .orange : .gray)
|
||||||
.padding(.leading, 1.5)
|
|
||||||
|
|
||||||
Text(NSLocalizedString("notifications", comment: "Notifications"))
|
if unreadCount > 0 {
|
||||||
.font(.caption2)
|
Text("\(min(unreadCount, 99))")
|
||||||
.fontWeight(.bold)
|
.font(.system(size: 10, weight: .bold))
|
||||||
|
.foregroundColor(.white)
|
||||||
Spacer()
|
.padding(4)
|
||||||
}
|
.background(Circle().fill(Color.blue))
|
||||||
|
.offset(x: 12, y: -12)
|
||||||
if unreadCount > 0 {
|
|
||||||
HStack(spacing: 4) {
|
|
||||||
Text("\(unreadCount)")
|
|
||||||
.font(.caption2)
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.padding(.horizontal, 6)
|
|
||||||
.background(
|
|
||||||
Capsule()
|
|
||||||
.fill(Color.blue.opacity(0.5))
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(NSLocalizedString("unread", comment: "unread"))
|
|
||||||
.font(.caption2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Image(systemName: "bell.fill")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
.padding(.leading, 1.5)
|
||||||
|
|
||||||
Text("on the Solar Network")
|
Text(NSLocalizedString("notifications", comment: "Notifications"))
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.secondary)
|
.fontWeight(.bold)
|
||||||
.padding(.horizontal, 1.5)
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
if unreadCount > 0 {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Text("\(unreadCount)")
|
||||||
|
.font(.caption2)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.padding(.horizontal, 6)
|
||||||
|
.background(
|
||||||
|
Capsule()
|
||||||
|
.fill(Color.blue.opacity(0.5))
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(NSLocalizedString("unread", comment: "unread"))
|
||||||
|
.font(.caption2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("on the Solar Network")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.horizontal, 1.5)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
@@ -490,79 +519,100 @@ struct NotificationWidgetEntryView: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func EmptyView() -> some View {
|
private func EmptyView() -> some View {
|
||||||
Link(destination: URL(string: "solian://notifications")!) {
|
Link(destination: URL(string: "solian://notifications")!) {
|
||||||
VStack(alignment: .leading, spacing: isAccessory ? 4 : 8) {
|
if isCircular {
|
||||||
HStack(spacing: 6) {
|
ZStack {
|
||||||
Image(systemName: "bell")
|
Image(systemName: "bell")
|
||||||
.font(isAccessory ? .caption : .title3)
|
.font(.system(size: 20))
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
Text(NSLocalizedString("notifications", comment: "Notifications"))
|
|
||||||
.font(isAccessory ? .caption2 : .headline)
|
|
||||||
.fontWeight(.bold)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
VStack(alignment: .leading, spacing: isAccessory ? 4 : 8) {
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Image(systemName: "bell")
|
||||||
|
.font(isAccessory ? .caption : .title3)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
if !isAccessory {
|
Text(NSLocalizedString("notifications", comment: "Notifications"))
|
||||||
Text(NSLocalizedString("noNotifications", comment: "No notifications yet"))
|
.font(isAccessory ? .caption2 : .headline)
|
||||||
.font(.caption)
|
.fontWeight(.bold)
|
||||||
.foregroundColor(.secondary)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAccessory {
|
||||||
|
Text(NSLocalizedString("noNotifications", comment: "No notifications yet"))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.padding(isAccessory ? 4 : 12)
|
||||||
}
|
}
|
||||||
.padding(isAccessory ? 4 : 12)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func LoadingView() -> some View {
|
private func LoadingView() -> some View {
|
||||||
VStack(alignment: .leading, spacing: isAccessory ? 4 : 8) {
|
if isCircular {
|
||||||
HStack(spacing: 6) {
|
ProgressView()
|
||||||
ProgressView()
|
.scaleEffect(0.8)
|
||||||
.scaleEffect(isAccessory ? 0.6 : 0.8)
|
} else {
|
||||||
Text(NSLocalizedString("loading", comment: "Loading..."))
|
VStack(alignment: .leading, spacing: isAccessory ? 4 : 8) {
|
||||||
.font(isAccessory ? .caption2 : .caption)
|
HStack(spacing: 6) {
|
||||||
.foregroundColor(.secondary)
|
ProgressView()
|
||||||
Spacer()
|
.scaleEffect(isAccessory ? 0.6 : 0.8)
|
||||||
}
|
Text(NSLocalizedString("loading", comment: "Loading..."))
|
||||||
|
.font(isAccessory ? .caption2 : .caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
if !isAccessory {
|
if !isAccessory {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.padding(isAccessory ? 4 : 12)
|
||||||
}
|
}
|
||||||
.padding(isAccessory ? 4 : 12)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func ErrorView(error: String) -> some View {
|
private func ErrorView(error: String) -> some View {
|
||||||
Link(destination: URL(string: "solian://notifications")!) {
|
Link(destination: URL(string: "solian://notifications")!) {
|
||||||
VStack(alignment: .leading, spacing: isAccessory ? 4 : 8) {
|
if isCircular {
|
||||||
HStack(spacing: 6) {
|
ZStack {
|
||||||
Image(systemName: "exclamationmark.triangle")
|
Image(systemName: "exclamationmark.triangle")
|
||||||
.foregroundColor(.secondary)
|
.font(.system(size: 20))
|
||||||
.font(isAccessory ? .caption : .title3)
|
.foregroundColor(.red)
|
||||||
|
|
||||||
Text(NSLocalizedString("error", comment: "Error"))
|
|
||||||
.font(isAccessory ? .caption2 : .headline)
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
VStack(alignment: .leading, spacing: isAccessory ? 4 : 8) {
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Image(systemName: "exclamationmark.triangle")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.font(isAccessory ? .caption : .title3)
|
||||||
|
|
||||||
if !isAccessory {
|
Text(NSLocalizedString("error", comment: "Error"))
|
||||||
Text(NSLocalizedString("openAppToRefresh", comment: "Open app to refresh"))
|
.font(isAccessory ? .caption2 : .headline)
|
||||||
.font(.caption)
|
Spacer()
|
||||||
.foregroundColor(.secondary)
|
}
|
||||||
|
|
||||||
Text(error)
|
if !isAccessory {
|
||||||
.font(.footnote)
|
Text(NSLocalizedString("openAppToRefresh", comment: "Open app to refresh"))
|
||||||
.foregroundStyle(.secondary)
|
.font(.caption)
|
||||||
.lineLimit(nil)
|
.foregroundColor(.secondary)
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
|
|
||||||
Spacer()
|
Text(error)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(nil)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.padding(isAccessory ? 4 : 12)
|
||||||
}
|
}
|
||||||
.padding(isAccessory ? 4 : 12)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,13 +697,14 @@ struct SolianNotificationWidget: Widget {
|
|||||||
|
|
||||||
private var supportedFamilies: [WidgetFamily] {
|
private var supportedFamilies: [WidgetFamily] {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
return [.systemSmall, .systemMedium, .systemLarge, .accessoryRectangular]
|
return [.systemSmall, .systemMedium, .systemLarge, .accessoryRectangular, .accessoryCircular]
|
||||||
#else
|
#else
|
||||||
return [.systemSmall, .systemMedium, .systemLarge]
|
return [.systemSmall, .systemMedium, .systemLarge]
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
#Preview(as: .accessoryRectangular) {
|
#Preview(as: .accessoryRectangular) {
|
||||||
SolianNotificationWidget()
|
SolianNotificationWidget()
|
||||||
} timeline: {
|
} timeline: {
|
||||||
@@ -694,6 +745,7 @@ struct SolianNotificationWidget: Widget {
|
|||||||
isLoading: false
|
isLoading: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#Preview(as: .systemSmall) {
|
#Preview(as: .systemSmall) {
|
||||||
SolianNotificationWidget()
|
SolianNotificationWidget()
|
||||||
@@ -846,4 +898,31 @@ struct SolianNotificationWidget: Widget {
|
|||||||
isLoading: false
|
isLoading: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview(as: .accessoryCircular) {
|
||||||
|
SolianNotificationWidget()
|
||||||
|
} timeline: {
|
||||||
|
NotificationEntry(
|
||||||
|
date: .now,
|
||||||
|
notifications: [
|
||||||
|
SnNotification(
|
||||||
|
id: "1",
|
||||||
|
topic: "post.replies",
|
||||||
|
title: "New reply",
|
||||||
|
subtitle: "Someone replied",
|
||||||
|
content: "Content",
|
||||||
|
meta: nil,
|
||||||
|
priority: 0,
|
||||||
|
viewedAt: nil,
|
||||||
|
accountId: "acc-1",
|
||||||
|
createdAt: ISO8601DateFormatter().string(from: Date()),
|
||||||
|
updatedAt: ISO8601DateFormatter().string(from: Date()),
|
||||||
|
deletedAt: nil
|
||||||
|
)
|
||||||
|
],
|
||||||
|
unreadCount: 5,
|
||||||
|
error: nil,
|
||||||
|
isLoading: false
|
||||||
|
)
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user