|
@@ -14,8 +14,6 @@ struct DriveDesktopWidgetView: View {
|
|
|
switch mode {
|
|
switch mode {
|
|
|
case .recentQuick:
|
|
case .recentQuick:
|
|
|
recentQuickBody
|
|
recentQuickBody
|
|
|
- case .filesPreview:
|
|
|
|
|
- filesPreviewBody
|
|
|
|
|
case .interactive:
|
|
case .interactive:
|
|
|
interactiveBody
|
|
interactiveBody
|
|
|
}
|
|
}
|
|
@@ -25,54 +23,49 @@ struct DriveDesktopWidgetView: View {
|
|
|
|
|
|
|
|
// MARK: - Small
|
|
// MARK: - Small
|
|
|
|
|
|
|
|
- private struct RecentEntry: Identifiable {
|
|
|
|
|
|
|
+ private struct DriveQuickAction: Identifiable {
|
|
|
let id: String
|
|
let id: String
|
|
|
- let name: String
|
|
|
|
|
|
|
+ let title: String
|
|
|
let systemImage: String
|
|
let systemImage: String
|
|
|
- let time: String
|
|
|
|
|
|
|
+ let path: String
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static let recentSamples: [RecentEntry] = [
|
|
|
|
|
- RecentEntry(id: "1", name: "Q4 Budget.xlsx", systemImage: "tablecells", time: "2h ago"),
|
|
|
|
|
- RecentEntry(id: "2", name: "Project Notes", systemImage: "doc.text", time: "Yesterday"),
|
|
|
|
|
- RecentEntry(id: "3", name: "Design mockup.png", systemImage: "photo", time: "Mon"),
|
|
|
|
|
|
|
+ private static let smallQuickActions: [DriveQuickAction] = [
|
|
|
|
|
+ DriveQuickAction(id: "recent", title: "Recent", systemImage: "clock.fill", path: "/drive/recent"),
|
|
|
|
|
+ DriveQuickAction(id: "starred", title: "Starred", systemImage: "star.fill", path: "/drive/starred"),
|
|
|
|
|
+ DriveQuickAction(id: "shared", title: "Shared", systemImage: "person.2.fill", path: "/drive/shared-with-me"),
|
|
|
|
|
+ DriveQuickAction(id: "upload", title: "Upload", systemImage: "arrow.up.doc.fill", path: "/drive/home"),
|
|
|
|
|
+ DriveQuickAction(id: "folders", title: "Folders", systemImage: "folder.fill", path: "/drive/folders"),
|
|
|
|
|
+ DriveQuickAction(id: "trash", title: "Trash", systemImage: "trash.fill", path: "/drive/trash"),
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
private var recentQuickBody: some View {
|
|
private var recentQuickBody: some View {
|
|
|
ZStack(alignment: .topLeading) {
|
|
ZStack(alignment: .topLeading) {
|
|
|
driveGradientBackground
|
|
driveGradientBackground
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
- VStack(alignment: .leading, spacing: 10) {
|
|
|
|
|
- HStack(spacing: 10) {
|
|
|
|
|
|
|
+ VStack(alignment: .leading, spacing: 9) {
|
|
|
|
|
+ HStack(spacing: 8) {
|
|
|
AppIconView(app: app, size: 32, showAppBackground: false, iconPaddingFactor: 0.08)
|
|
AppIconView(app: app, size: 32, showAppBackground: false, iconPaddingFactor: 0.08)
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
|
Text(app.name)
|
|
Text(app.name)
|
|
|
.font(.system(size: 14, weight: .bold))
|
|
.font(.system(size: 14, weight: .bold))
|
|
|
.foregroundStyle(.white.opacity(0.95))
|
|
.foregroundStyle(.white.opacity(0.95))
|
|
|
- Text("Recent files")
|
|
|
|
|
|
|
+ Text("Quick actions")
|
|
|
.font(.system(size: 10, weight: .semibold))
|
|
.font(.system(size: 10, weight: .semibold))
|
|
|
.foregroundStyle(.white.opacity(0.65))
|
|
.foregroundStyle(.white.opacity(0.65))
|
|
|
}
|
|
}
|
|
|
Spacer(minLength: 0)
|
|
Spacer(minLength: 0)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Text("Sample")
|
|
|
|
|
- .font(.system(size: 9, weight: .bold))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.45))
|
|
|
|
|
-
|
|
|
|
|
- VStack(spacing: 6) {
|
|
|
|
|
- ForEach(Self.recentSamples) { item in
|
|
|
|
|
- recentRow(name: item.name, systemImage: item.systemImage, time: item.time) {
|
|
|
|
|
- openDriveSearch(query: item.name)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ smallActionsGrid
|
|
|
|
|
+ .padding(.top, 3)
|
|
|
|
|
|
|
|
drivePrimaryButton(title: "Open Drive", systemImage: "externaldrive.fill") {
|
|
drivePrimaryButton(title: "Open Drive", systemImage: "externaldrive.fill") {
|
|
|
openDrivePath("/drive/home")
|
|
openDrivePath("/drive/home")
|
|
|
}
|
|
}
|
|
|
|
|
+ .padding(.top, 3)
|
|
|
}
|
|
}
|
|
|
- .padding(14)
|
|
|
|
|
|
|
+ .padding(12)
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
}
|
|
}
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
@@ -80,166 +73,38 @@ struct DriveDesktopWidgetView: View {
|
|
|
.opacity(isPreview ? 0.85 : 1)
|
|
.opacity(isPreview ? 0.85 : 1)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func recentRow(name: String, systemImage: String, time: String, action: @escaping () -> Void) -> some View {
|
|
|
|
|
- Button(action: {
|
|
|
|
|
- NSApp.activate(ignoringOtherApps: true)
|
|
|
|
|
- action()
|
|
|
|
|
- }) {
|
|
|
|
|
- HStack(spacing: 10) {
|
|
|
|
|
- Image(systemName: systemImage)
|
|
|
|
|
- .font(.system(size: 14, weight: .semibold))
|
|
|
|
|
- .foregroundStyle(Color(red: 0.75, green: 0.92, blue: 0.82))
|
|
|
|
|
- .frame(width: 28, height: 28)
|
|
|
|
|
- .background(RoundedRectangle(cornerRadius: 8).fill(Color.white.opacity(0.12)))
|
|
|
|
|
- VStack(alignment: .leading, spacing: 2) {
|
|
|
|
|
- Text(name)
|
|
|
|
|
- .font(.system(size: 12, weight: .semibold))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.95))
|
|
|
|
|
- .lineLimit(1)
|
|
|
|
|
- Text(time)
|
|
|
|
|
- .font(.system(size: 10, weight: .medium))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.5))
|
|
|
|
|
- }
|
|
|
|
|
- Spacer(minLength: 0)
|
|
|
|
|
- Image(systemName: "chevron.right")
|
|
|
|
|
- .font(.system(size: 10, weight: .bold))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.35))
|
|
|
|
|
- }
|
|
|
|
|
- .padding(10)
|
|
|
|
|
- .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.black.opacity(0.22)))
|
|
|
|
|
- }
|
|
|
|
|
- .buttonStyle(.plain)
|
|
|
|
|
- .disabled(isPreview)
|
|
|
|
|
- .allowsHitTesting(!isPreview)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // MARK: - Medium
|
|
|
|
|
-
|
|
|
|
|
- private struct StarredEntry: Identifiable {
|
|
|
|
|
- let id: String
|
|
|
|
|
- let name: String
|
|
|
|
|
- let systemImage: String
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private static let starredSamples: [StarredEntry] = [
|
|
|
|
|
- StarredEntry(id: "s1", name: "Roadmap 2026", systemImage: "rectangle.on.rectangle"),
|
|
|
|
|
- StarredEntry(id: "s2", name: "Team roster", systemImage: "doc.richtext"),
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- private var filesPreviewBody: some View {
|
|
|
|
|
- ZStack(alignment: .topLeading) {
|
|
|
|
|
- driveGradientBackground
|
|
|
|
|
- .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
|
- ScrollView {
|
|
|
|
|
- VStack(alignment: .leading, spacing: 12) {
|
|
|
|
|
- HStack(spacing: 8) {
|
|
|
|
|
- AppIconView(app: app, size: 30, showAppBackground: false, iconPaddingFactor: 0.08)
|
|
|
|
|
- Text("Drive")
|
|
|
|
|
- .font(.system(size: 14, weight: .bold))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.95))
|
|
|
|
|
- Spacer(minLength: 0)
|
|
|
|
|
- Text("Sample")
|
|
|
|
|
- .font(.system(size: 10, weight: .bold))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.5))
|
|
|
|
|
- .padding(.horizontal, 8)
|
|
|
|
|
- .padding(.vertical, 4)
|
|
|
|
|
- .background(Capsule().fill(Color.white.opacity(0.12)))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- drivePrimaryButton(title: "Open Drive", systemImage: "externaldrive.fill") {
|
|
|
|
|
- openDrivePath("/drive/home")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- sectionTitle("Recent")
|
|
|
|
|
- VStack(spacing: 8) {
|
|
|
|
|
- ForEach(Self.recentSamples) { item in
|
|
|
|
|
- fileActionRow(
|
|
|
|
|
- name: item.name,
|
|
|
|
|
- systemImage: item.systemImage,
|
|
|
|
|
- subtitle: item.time,
|
|
|
|
|
- onOpen: { openDrivePath("/drive/recent") },
|
|
|
|
|
- onShare: { openDriveSearch(query: item.name) }
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- sectionTitle("Starred")
|
|
|
|
|
- VStack(spacing: 8) {
|
|
|
|
|
- ForEach(Self.starredSamples) { item in
|
|
|
|
|
- fileActionRow(
|
|
|
|
|
- name: item.name,
|
|
|
|
|
- systemImage: item.systemImage,
|
|
|
|
|
- subtitle: "Starred",
|
|
|
|
|
- onOpen: { openDrivePath("/drive/starred") },
|
|
|
|
|
- onShare: { openDriveSearch(query: item.name) }
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- .frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
|
- .padding(.bottom, 6)
|
|
|
|
|
- }
|
|
|
|
|
- .padding(14)
|
|
|
|
|
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
|
|
- }
|
|
|
|
|
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
|
|
- .disabled(isPreview)
|
|
|
|
|
- .opacity(isPreview ? 0.85 : 1)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func sectionTitle(_ text: String) -> some View {
|
|
|
|
|
- Text(text)
|
|
|
|
|
- .font(.system(size: 11, weight: .bold))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.62))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func fileActionRow(
|
|
|
|
|
- name: String,
|
|
|
|
|
- systemImage: String,
|
|
|
|
|
- subtitle: String,
|
|
|
|
|
- onOpen: @escaping () -> Void,
|
|
|
|
|
- onShare: @escaping () -> Void
|
|
|
|
|
- ) -> some View {
|
|
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
|
|
- HStack(spacing: 10) {
|
|
|
|
|
- Image(systemName: systemImage)
|
|
|
|
|
- .font(.system(size: 13, weight: .semibold))
|
|
|
|
|
- .foregroundStyle(Color(red: 0.75, green: 0.92, blue: 0.82))
|
|
|
|
|
- VStack(alignment: .leading, spacing: 2) {
|
|
|
|
|
- Text(name)
|
|
|
|
|
- .font(.system(size: 12, weight: .bold))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.95))
|
|
|
|
|
- .lineLimit(1)
|
|
|
|
|
- Text(subtitle)
|
|
|
|
|
- .font(.system(size: 10, weight: .medium))
|
|
|
|
|
- .foregroundStyle(.white.opacity(0.5))
|
|
|
|
|
- }
|
|
|
|
|
- Spacer(minLength: 0)
|
|
|
|
|
- }
|
|
|
|
|
- HStack(spacing: 8) {
|
|
|
|
|
- smallAction(title: "Open", systemImage: "arrow.up.right.square", action: onOpen)
|
|
|
|
|
- smallAction(title: "Share", systemImage: "square.and.arrow.up", action: onShare)
|
|
|
|
|
|
|
+ private var smallActionsGrid: some View {
|
|
|
|
|
+ let cols = [GridItem(.flexible(minimum: 0), spacing: 8), GridItem(.flexible(minimum: 0), spacing: 8)]
|
|
|
|
|
+ return LazyVGrid(columns: cols, spacing: 8) {
|
|
|
|
|
+ ForEach(Self.smallQuickActions) { action in
|
|
|
|
|
+ smallQuickActionButton(action)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- .padding(10)
|
|
|
|
|
- .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.black.opacity(0.24)))
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func smallAction(title: String, systemImage: String, action: @escaping () -> Void) -> some View {
|
|
|
|
|
|
|
+ private func smallQuickActionButton(_ actionItem: DriveQuickAction) -> some View {
|
|
|
Button(action: {
|
|
Button(action: {
|
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
|
- action()
|
|
|
|
|
|
|
+ openDrivePath(actionItem.path)
|
|
|
}) {
|
|
}) {
|
|
|
- HStack(spacing: 4) {
|
|
|
|
|
- Image(systemName: systemImage)
|
|
|
|
|
- .font(.system(size: 10, weight: .semibold))
|
|
|
|
|
- Text(title)
|
|
|
|
|
- .font(.system(size: 10, weight: .bold))
|
|
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ Image(systemName: actionItem.systemImage)
|
|
|
|
|
+ .font(.system(size: 11, weight: .semibold))
|
|
|
|
|
+ Text(actionItem.title)
|
|
|
|
|
+ .font(.system(size: 11, weight: .bold))
|
|
|
|
|
+ .lineLimit(1)
|
|
|
}
|
|
}
|
|
|
- .foregroundStyle(.white.opacity(0.9))
|
|
|
|
|
- .padding(.horizontal, 10)
|
|
|
|
|
- .padding(.vertical, 6)
|
|
|
|
|
- .background(Capsule().fill(Color.white.opacity(0.14)))
|
|
|
|
|
|
|
+ .foregroundStyle(.white.opacity(0.94))
|
|
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
|
|
+ .padding(.vertical, 8)
|
|
|
|
|
+ .background(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
|
|
|
+ .fill(Color.black.opacity(0.24))
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
|
|
|
+ .stroke(Color.white.opacity(0.12), lineWidth: 1)
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
.buttonStyle(.plain)
|
|
|
.disabled(isPreview)
|
|
.disabled(isPreview)
|