mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
89 lines
2.9 KiB
Swift
89 lines
2.9 KiB
Swift
//
|
|
// LockScreenLiveActivityOverlay.swift
|
|
// DynamicIsland
|
|
//
|
|
// Mirrors the closed-notch live activity layout on the macOS lock screen.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
enum LockLiveActivityState: Equatable {
|
|
case locked
|
|
case unlocking
|
|
}
|
|
|
|
final class LockScreenLiveActivityOverlayViewModel: ObservableObject {
|
|
@Published var expansion: CGFloat = 1
|
|
}
|
|
|
|
struct LockScreenLiveActivityOverlay: View {
|
|
let state: LockLiveActivityState
|
|
let notchSize: CGSize
|
|
@ObservedObject var viewModel: LockScreenLiveActivityOverlayViewModel
|
|
|
|
private var indicatorSize: CGFloat {
|
|
max(0, notchSize.height - 12)
|
|
}
|
|
|
|
private var horizontalPadding: CGFloat {
|
|
cornerRadiusInsets.closed.bottom
|
|
}
|
|
|
|
private var baseWidth: CGFloat {
|
|
notchSize.width + (indicatorSize * 2)
|
|
}
|
|
|
|
private var totalWidth: CGFloat {
|
|
baseWidth + (horizontalPadding * 2)
|
|
}
|
|
|
|
private var iconName: String {
|
|
state == .locked ? "lock.fill" : "lock.open.fill"
|
|
}
|
|
|
|
private var iconColor: Color { .white }
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 0) {
|
|
Color.clear
|
|
.overlay(alignment: .leading) {
|
|
Image(systemName: iconName)
|
|
.font(.system(size: 12, weight: .semibold))
|
|
.foregroundStyle(iconColor)
|
|
.frame(width: indicatorSize, height: indicatorSize)
|
|
}
|
|
.frame(width: indicatorSize, height: notchSize.height, alignment: .top)
|
|
|
|
Color.clear
|
|
.frame(width: notchSize.width, height: notchSize.height, alignment: .top)
|
|
|
|
Color.clear
|
|
.frame(width: indicatorSize, height: notchSize.height, alignment: .top)
|
|
}
|
|
.frame(width: baseWidth, height: notchSize.height)
|
|
.padding(.horizontal, horizontalPadding)
|
|
.background(Color.black)
|
|
.clipShape(
|
|
NotchShape(
|
|
topCornerRadius: cornerRadiusInsets.closed.top,
|
|
bottomCornerRadius: cornerRadiusInsets.closed.bottom
|
|
)
|
|
)
|
|
.frame(width: totalWidth, height: notchSize.height)
|
|
.scaleEffect(x: max(0.05, viewModel.expansion), y: 1, anchor: .center)
|
|
.opacity(min(1, max(0.2, viewModel.expansion * 5)))
|
|
.animation(.spring(response: 0.45, dampingFraction: 0.85), value: state)
|
|
.animation(.spring(response: 0.5, dampingFraction: 0.85), value: viewModel.expansion)
|
|
.drawingGroup()
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
let model = LockScreenLiveActivityOverlayViewModel()
|
|
return VStack(spacing: 24) {
|
|
LockScreenLiveActivityOverlay(state: .locked, notchSize: CGSize(width: 220, height: 32), viewModel: model)
|
|
LockScreenLiveActivityOverlay(state: .unlocking, notchSize: CGSize(width: 220, height: 32), viewModel: model)
|
|
}
|
|
.padding()
|
|
.background(Color.gray.opacity(0.2))
|
|
}
|