mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
BugFix: LiveActivity expands too much
This commit is contained in:
parent
e500f5a5b2
commit
f8466c2027
5 changed files with 162 additions and 16 deletions
|
|
@ -3569,6 +3569,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Bottom Pad:" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Brightness HUD" : {
|
"Brightness HUD" : {
|
||||||
|
|
||||||
|
|
@ -5777,6 +5780,9 @@
|
||||||
},
|
},
|
||||||
"Display Mode" : {
|
"Display Mode" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Display Options" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Displays a HUD notification when Bluetooth audio devices (headphones, AirPods, speakers) connect, showing device name and battery level." : {
|
"Displays a HUD notification when Bluetooth audio devices (headphones, AirPods, speakers) connect, showing device name and battery level." : {
|
||||||
"comment" : "A footer for the \"Bluetooth Audio Devices\" section in the HUD settings.",
|
"comment" : "A footer for the \"Bluetooth Audio Devices\" section in the HUD settings.",
|
||||||
|
|
@ -6213,6 +6219,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Edit Animation" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Edit layout" : {
|
"Edit layout" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -7175,6 +7184,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Expand Notch with Animation" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Extensions" : {
|
"Extensions" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -13845,6 +13857,9 @@
|
||||||
},
|
},
|
||||||
"Save" : {
|
"Save" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Save Changes" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Save Configuration" : {
|
"Save Configuration" : {
|
||||||
|
|
||||||
|
|
@ -17496,6 +17511,9 @@
|
||||||
},
|
},
|
||||||
"When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls." : {
|
"When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"When enabled, the Dynamic Island will expand horizontally to match the animation size" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,16 @@ private struct AnimationContentView: View {
|
||||||
speed: animation.speed,
|
speed: animation.speed,
|
||||||
loopMode: .loop
|
loopMode: .loop
|
||||||
))
|
))
|
||||||
.frame(width: config.cropWidth * config.scale,
|
.frame(
|
||||||
height: config.cropHeight * config.scale)
|
width: config.cropWidth * config.scale,
|
||||||
|
height: config.cropHeight * config.scale
|
||||||
|
)
|
||||||
.offset(x: config.offsetX, y: config.offsetY)
|
.offset(x: config.offsetX, y: config.offsetY)
|
||||||
.rotationEffect(.degrees(config.rotation))
|
.rotationEffect(.degrees(config.rotation))
|
||||||
.opacity(config.opacity)
|
.opacity(config.opacity)
|
||||||
|
.padding(.bottom, config.paddingBottom)
|
||||||
|
.frame(width: config.expandWithAnimation ? nil : 30, height: 20)
|
||||||
|
.clipped()
|
||||||
|
|
||||||
case .lottieURL(let url):
|
case .lottieURL(let url):
|
||||||
LottieView(state: LUStateData(
|
LottieView(state: LUStateData(
|
||||||
|
|
@ -52,11 +57,16 @@ private struct AnimationContentView: View {
|
||||||
speed: animation.speed,
|
speed: animation.speed,
|
||||||
loopMode: .loop
|
loopMode: .loop
|
||||||
))
|
))
|
||||||
.frame(width: config.cropWidth * config.scale,
|
.frame(
|
||||||
height: config.cropHeight * config.scale)
|
width: config.cropWidth * config.scale,
|
||||||
|
height: config.cropHeight * config.scale
|
||||||
|
)
|
||||||
.offset(x: config.offsetX, y: config.offsetY)
|
.offset(x: config.offsetX, y: config.offsetY)
|
||||||
.rotationEffect(.degrees(config.rotation))
|
.rotationEffect(.degrees(config.rotation))
|
||||||
.opacity(config.opacity)
|
.opacity(config.opacity)
|
||||||
|
.padding(.bottom, config.paddingBottom)
|
||||||
|
.frame(width: config.expandWithAnimation ? nil : 30, height: 20)
|
||||||
|
.clipped()
|
||||||
|
|
||||||
case .builtInFace:
|
case .builtInFace:
|
||||||
MinimalFaceFeatures(height: 20, width: 30)
|
MinimalFaceFeatures(height: 20, width: 30)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ struct AnimationEditorView: View {
|
||||||
let sourceURL: URL
|
let sourceURL: URL
|
||||||
let isRemoteURL: Bool
|
let isRemoteURL: Bool
|
||||||
@Binding var animation: CustomIdleAnimation?
|
@Binding var animation: CustomIdleAnimation?
|
||||||
|
let existingAnimation: CustomIdleAnimation?
|
||||||
|
|
||||||
@State private var name: String
|
@State private var name: String
|
||||||
@State private var speed: CGFloat = 1.0
|
@State private var speed: CGFloat = 1.0
|
||||||
|
|
@ -26,6 +27,8 @@ struct AnimationEditorView: View {
|
||||||
@State private var cropHeight: CGFloat = 20
|
@State private var cropHeight: CGFloat = 20
|
||||||
@State private var rotation: CGFloat = 0
|
@State private var rotation: CGFloat = 0
|
||||||
@State private var opacity: CGFloat = 1.0
|
@State private var opacity: CGFloat = 1.0
|
||||||
|
@State private var paddingBottom: CGFloat = 0
|
||||||
|
@State private var expandWithAnimation: Bool = false
|
||||||
|
|
||||||
@State private var showingError = false
|
@State private var showingError = false
|
||||||
@State private var errorMessage = ""
|
@State private var errorMessage = ""
|
||||||
|
|
@ -34,21 +37,37 @@ struct AnimationEditorView: View {
|
||||||
// Preview state
|
// Preview state
|
||||||
@State private var previewScale: CGFloat = 1.0
|
@State private var previewScale: CGFloat = 1.0
|
||||||
|
|
||||||
init(sourceURL: URL, isRemoteURL: Bool, animation: Binding<CustomIdleAnimation?>) {
|
init(sourceURL: URL, isRemoteURL: Bool, animation: Binding<CustomIdleAnimation?>, existingAnimation: CustomIdleAnimation? = nil) {
|
||||||
self.sourceURL = sourceURL
|
self.sourceURL = sourceURL
|
||||||
self.isRemoteURL = isRemoteURL
|
self.isRemoteURL = isRemoteURL
|
||||||
self._animation = animation
|
self._animation = animation
|
||||||
|
self.existingAnimation = existingAnimation
|
||||||
|
|
||||||
// Initialize name from URL
|
// Initialize from existing animation if editing, otherwise from URL
|
||||||
let fileName = sourceURL.deletingPathExtension().lastPathComponent
|
if let existing = existingAnimation {
|
||||||
_name = State(initialValue: fileName)
|
_name = State(initialValue: existing.name)
|
||||||
|
_speed = State(initialValue: existing.speed)
|
||||||
|
let config = existing.transformConfig
|
||||||
|
_scale = State(initialValue: config.scale)
|
||||||
|
_offsetX = State(initialValue: config.offsetX)
|
||||||
|
_offsetY = State(initialValue: config.offsetY)
|
||||||
|
_cropWidth = State(initialValue: config.cropWidth)
|
||||||
|
_cropHeight = State(initialValue: config.cropHeight)
|
||||||
|
_rotation = State(initialValue: config.rotation)
|
||||||
|
_opacity = State(initialValue: config.opacity)
|
||||||
|
_paddingBottom = State(initialValue: config.paddingBottom)
|
||||||
|
_expandWithAnimation = State(initialValue: config.expandWithAnimation)
|
||||||
|
} else {
|
||||||
|
let fileName = sourceURL.deletingPathExtension().lastPathComponent
|
||||||
|
_name = State(initialValue: fileName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Header
|
// Header
|
||||||
HStack {
|
HStack {
|
||||||
Text("Customize Animation")
|
Text(existingAnimation != nil ? "Edit Animation" : "Customize Animation")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
|
||||||
|
|
@ -94,6 +113,7 @@ struct AnimationEditorView: View {
|
||||||
.offset(x: offsetX * previewScale, y: offsetY * previewScale)
|
.offset(x: offsetX * previewScale, y: offsetY * previewScale)
|
||||||
.rotationEffect(.degrees(rotation))
|
.rotationEffect(.degrees(rotation))
|
||||||
.opacity(opacity)
|
.opacity(opacity)
|
||||||
|
.padding(.bottom, paddingBottom * previewScale)
|
||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
|
@ -218,6 +238,21 @@ struct AnimationEditorView: View {
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Padding Bottom
|
||||||
|
HStack {
|
||||||
|
Text("Bottom Pad:")
|
||||||
|
.frame(width: 80, alignment: .leading)
|
||||||
|
Slider(value: $paddingBottom, in: -20...20)
|
||||||
|
Text(String(format: "%.1f", paddingBottom))
|
||||||
|
.frame(width: 50, alignment: .trailing)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Button("Reset") {
|
||||||
|
withAnimation { paddingBottom = 0 }
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.controlSize(.small)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
@ -282,6 +317,24 @@ struct AnimationEditorView: View {
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
|
// Display Options
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
Text("Display Options")
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
Toggle(isOn: $expandWithAnimation) {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("Expand Notch with Animation")
|
||||||
|
.font(.body)
|
||||||
|
Text("When enabled, the Dynamic Island will expand horizontally to match the animation size")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
// Presets
|
// Presets
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Text("Quick Presets")
|
Text("Quick Presets")
|
||||||
|
|
@ -296,6 +349,8 @@ struct AnimationEditorView: View {
|
||||||
offsetX = 0
|
offsetX = 0
|
||||||
offsetY = 0
|
offsetY = 0
|
||||||
rotation = 0
|
rotation = 0
|
||||||
|
paddingBottom = 0
|
||||||
|
expandWithAnimation = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
|
@ -307,6 +362,8 @@ struct AnimationEditorView: View {
|
||||||
cropHeight = 20
|
cropHeight = 20
|
||||||
offsetX = 0
|
offsetX = 0
|
||||||
offsetY = 0
|
offsetY = 0
|
||||||
|
paddingBottom = 0
|
||||||
|
expandWithAnimation = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
|
@ -314,6 +371,7 @@ struct AnimationEditorView: View {
|
||||||
Button("Scale Up 2x") {
|
Button("Scale Up 2x") {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
scale = 2.0
|
scale = 2.0
|
||||||
|
expandWithAnimation = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
|
@ -335,6 +393,8 @@ struct AnimationEditorView: View {
|
||||||
rotation = 0
|
rotation = 0
|
||||||
opacity = 1.0
|
opacity = 1.0
|
||||||
speed = 1.0
|
speed = 1.0
|
||||||
|
paddingBottom = 0
|
||||||
|
expandWithAnimation = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
|
@ -361,7 +421,7 @@ struct AnimationEditorView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button("Import") {
|
Button(existingAnimation != nil ? "Save Changes" : "Import") {
|
||||||
importAnimation()
|
importAnimation()
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
|
|
@ -393,10 +453,23 @@ struct AnimationEditorView: View {
|
||||||
cropWidth: cropWidth,
|
cropWidth: cropWidth,
|
||||||
cropHeight: cropHeight,
|
cropHeight: cropHeight,
|
||||||
rotation: rotation,
|
rotation: rotation,
|
||||||
opacity: opacity
|
opacity: opacity,
|
||||||
|
paddingBottom: paddingBottom,
|
||||||
|
expandWithAnimation: expandWithAnimation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Import with config
|
// If editing existing animation, update it directly
|
||||||
|
if let existing = existingAnimation {
|
||||||
|
var updatedAnimation = existing
|
||||||
|
updatedAnimation.name = name
|
||||||
|
updatedAnimation.speed = speed
|
||||||
|
updatedAnimation.transformConfig = config
|
||||||
|
animation = updatedAnimation
|
||||||
|
dismiss()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import with config (new animation)
|
||||||
let result = IdleAnimationManager.shared.importLottieFile(
|
let result = IdleAnimationManager.shared.importLottieFile(
|
||||||
from: sourceURL,
|
from: sourceURL,
|
||||||
name: name,
|
name: name,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ struct IdleAnimationsSettingsSection: View {
|
||||||
@State private var editorSourceURL: URL?
|
@State private var editorSourceURL: URL?
|
||||||
@State private var editorIsRemote = false
|
@State private var editorIsRemote = false
|
||||||
@State private var editedAnimation: CustomIdleAnimation?
|
@State private var editedAnimation: CustomIdleAnimation?
|
||||||
|
@State private var editingExistingAnimation: CustomIdleAnimation?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section {
|
Section {
|
||||||
|
|
@ -49,6 +50,18 @@ struct IdleAnimationsSettingsSection: View {
|
||||||
onDelete: animation.isBuiltIn ? nil : {
|
onDelete: animation.isBuiltIn ? nil : {
|
||||||
selectedForDeletion = animation
|
selectedForDeletion = animation
|
||||||
showingDeleteAlert = true
|
showingDeleteAlert = true
|
||||||
|
},
|
||||||
|
onEdit: animation.isBuiltIn ? nil : {
|
||||||
|
editingExistingAnimation = animation
|
||||||
|
if case .lottieFile(let url) = animation.source {
|
||||||
|
editorSourceURL = url
|
||||||
|
editorIsRemote = false
|
||||||
|
showingEditor = true
|
||||||
|
} else if case .lottieURL(let url) = animation.source {
|
||||||
|
editorSourceURL = url
|
||||||
|
editorIsRemote = true
|
||||||
|
showingEditor = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -107,19 +120,35 @@ struct IdleAnimationsSettingsSection: View {
|
||||||
AnimationEditorView(
|
AnimationEditorView(
|
||||||
sourceURL: url,
|
sourceURL: url,
|
||||||
isRemoteURL: editorIsRemote,
|
isRemoteURL: editorIsRemote,
|
||||||
animation: $editedAnimation
|
animation: $editedAnimation,
|
||||||
|
existingAnimation: editingExistingAnimation
|
||||||
)
|
)
|
||||||
.frame(minWidth: 800, minHeight: 600)
|
.frame(minWidth: 800, minHeight: 600)
|
||||||
.onChange(of: editedAnimation) { oldValue, newValue in
|
.onChange(of: editedAnimation) { oldValue, newValue in
|
||||||
if let animation = newValue {
|
if let animation = newValue {
|
||||||
// Animation was successfully imported via editor
|
// Animation was successfully imported/edited via editor
|
||||||
withAnimation {
|
if let existingAnim = editingExistingAnimation {
|
||||||
selectedIdleAnimation = animation
|
// Editing existing animation - update it
|
||||||
|
if var animations = customIdleAnimations as? [CustomIdleAnimation],
|
||||||
|
let index = animations.firstIndex(where: { $0.id == existingAnim.id }) {
|
||||||
|
animations[index] = animation
|
||||||
|
customIdleAnimations = animations
|
||||||
|
// Update selection if this was the selected animation
|
||||||
|
if selectedIdleAnimation?.id == existingAnim.id {
|
||||||
|
selectedIdleAnimation = animation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// New import - just select it
|
||||||
|
withAnimation {
|
||||||
|
selectedIdleAnimation = animation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showingEditor = false
|
showingEditor = false
|
||||||
// Reset editor state
|
// Reset editor state
|
||||||
editorSourceURL = nil
|
editorSourceURL = nil
|
||||||
editedAnimation = nil
|
editedAnimation = nil
|
||||||
|
editingExistingAnimation = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,6 +213,7 @@ struct AnimationPreviewCard: View {
|
||||||
let isSelected: Bool
|
let isSelected: Bool
|
||||||
let onSelect: () -> Void
|
let onSelect: () -> Void
|
||||||
let onDelete: (() -> Void)?
|
let onDelete: (() -> Void)?
|
||||||
|
let onEdit: (() -> Void)?
|
||||||
|
|
||||||
@State private var isHovering = false
|
@State private var isHovering = false
|
||||||
|
|
||||||
|
|
@ -262,6 +292,19 @@ struct AnimationPreviewCard: View {
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
onSelect()
|
onSelect()
|
||||||
}
|
}
|
||||||
|
.contextMenu {
|
||||||
|
if let onEdit = onEdit {
|
||||||
|
Button("Edit Animation") {
|
||||||
|
onEdit()
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
if let onDelete = onDelete {
|
||||||
|
Button("Delete", role: .destructive) {
|
||||||
|
onDelete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ struct AnimationTransformConfig: Codable, Hashable, Equatable {
|
||||||
var cropHeight: CGFloat = 20
|
var cropHeight: CGFloat = 20
|
||||||
var rotation: CGFloat = 0
|
var rotation: CGFloat = 0
|
||||||
var opacity: CGFloat = 1.0
|
var opacity: CGFloat = 1.0
|
||||||
|
var paddingBottom: CGFloat = 0 // Allow adjustment to fill notch from bottom
|
||||||
|
var expandWithAnimation: Bool = false // Whether notch should expand horizontally with animation
|
||||||
|
|
||||||
static let `default` = AnimationTransformConfig()
|
static let `default` = AnimationTransformConfig()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue