Skip to content

Commit

Permalink
✨ Animations
Browse files Browse the repository at this point in the history
TODO: check that this is localization-ready
  • Loading branch information
MrKai77 committed Jun 27, 2024
1 parent 5ef5fba commit 64012be
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 74 deletions.
11 changes: 9 additions & 2 deletions Loop/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,15 @@
}
}
},
"%lld Loops left" : {

"%lld %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$lld %2$@"
}
}
}
},
"99 problems, updates ain't one." : {

Expand Down
171 changes: 99 additions & 72 deletions Loop/Luminare/Theming/IconConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,39 @@ class IconConfigurationModel: ObservableObject {
}
}

@Published var showingLockedAlert = false
@Published var selectedLockedMessage: LocalizedStringKey = .init("")
let lockedMessages: [LocalizedStringKey] = [
"You don’t have that yet!",
"Who do you think you are, trying to access these top secret icons?",
"Patience is a virtue, and your key to this icon.",
"This icon is locked, but your potential is not!",
"Keep looping, and this icon will be yours in no time.",
"This icon is still under wraps, stay tuned!",
"Some icons are worth the wait, don't you think?",
"Not yet, but you're closer than you were yesterday!",
"Unlocking this icon is just a matter of time and loops.",
"This icon is like a fine wine, it needs more time.",
"Stay curious, and soon this icon will be within your reach.",
"Keep up the good work, and this icon will be your reward.",
"This icon is reserved for the most dedicated loopers.",
"Your journey is not yet complete, this icon awaits at the end.",
"In due time, this icon shall be revealed to you.",
"Patience, young looper, this icon is not far away.",
"The journey of a thousand loops begins with a single step.",
"Every loop brings you closer to the treasure that awaits.",
"With each loop, the lock on this icon weakens.",
"Loop after loop, your dedication carves the key to success.",
"The icons are not just unlocked; they're earned, loop by loop.",
"As the loops accumulate, so too will your collection of icons.",
"Think of each loop as a riddle, solving the mystery of the locked icon.",
"Your persistence in looping is the master key to all icons.",
"Loop around the obstacles; your reward is just beyond them.",
"Each loop you complete plants the seeds for icons to grow.",
"Like the moon's phases, your icons will reveal themselves in cycles of loops.",
"The icons await, hidden behind the veil of loops yet to be made."
]

private func handleNotificationChange() {
if notificationWhenIconUnlocked {
AppDelegate.sendNotification(Bundle.main.appName, "You will now be notified when you unlock a new icon.")
Expand All @@ -63,48 +96,14 @@ class IconConfigurationModel: ObservableObject {
}
}

func nextIconUnlockLoopCount(timesLooped: Int) -> Int? {
Icon.all.first { $0.unlockTime > timesLooped }?.unlockTime
func nextIconUnlockLoopCount(timesLooped: Int) -> Int {
Icon.all.first { $0.unlockTime > timesLooped }?.unlockTime ?? 0
}
}

struct IconConfigurationView: View {
@Environment(\.openURL) var openURL
@StateObject private var model = IconConfigurationModel()
@Default(.timesLooped) var timesLooped
@State private var showingLockedAlert = false
@State private var selectedLockedMessage = LocalizedStringKey("")

let lockedMessages: [LocalizedStringKey] = [
"You don’t have that yet!",
"Who do you think you are, trying to access these top secret icons?",
"Patience is a virtue, and your key to this icon.",
"This icon is locked, but your potential is not!",
"Keep looping, and this icon will be yours in no time.",
"This icon is still under wraps, stay tuned!",
"Some icons are worth the wait, don't you think?",
"Not yet, but you're closer than you were yesterday!",
"Unlocking this icon is just a matter of time and loops.",
"This icon is like a fine wine, it needs more time.",
"Stay curious, and soon this icon will be within your reach.",
"Keep up the good work, and this icon will be your reward.",
"This icon is reserved for the most dedicated loopers.",
"Your journey is not yet complete, this icon awaits at the end.",
"In due time, this icon shall be revealed to you.",
"Patience, young looper, this icon is not far away.",
"The journey of a thousand loops begins with a single step.",
"Every loop brings you closer to the treasure that awaits.",
"With each loop, the lock on this icon weakens.",
"Loop after loop, your dedication carves the key to success.",
"The icons are not just unlocked; they're earned, loop by loop.",
"As the loops accumulate, so too will your collection of icons.",
"Think of each loop as a riddle, solving the mystery of the locked icon.",
"Your persistence in looping is the master key to all icons.",
"Loop around the obstacles; your reward is just beyond them.",
"Each loop you complete plants the seeds for icons to grow.",
"Like the moon's phases, your icons will reveal themselves in cycles of loops.",
"The icons await, hidden behind the veil of loops yet to be made."
]

var body: some View {
LuminareSection(showDividers: false) {
Expand All @@ -116,43 +115,15 @@ struct IconConfigurationView: View {
),
roundBottom: false
) { icon in
Group {
if icon.selectable {
Image(nsImage: NSImage(named: icon.iconName)!)
.resizable()
.aspectRatio(contentMode: .fit)
.padding(10)
} else {
VStack(alignment: .center) {
Spacer()
Image(._18PxLock)
if let nextUnlockCount = model.nextIconUnlockLoopCount(timesLooped: timesLooped),
nextUnlockCount == icon.unlockTime {
Text("\(nextUnlockCount - timesLooped) Loops left")
.font(.caption)
.foregroundColor(.secondary)
} else {
Text("Locked")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
selectedLockedMessage = lockedMessages.randomElement() ?? ""
showingLockedAlert = true
}
IconVew(model: model, icon: icon)
.aspectRatio(1, contentMode: .fit)
.alert(isPresented: $model.showingLockedAlert) {
Alert(
title: Text("Icon Locked"),
message: Text(model.selectedLockedMessage),
dismissButton: .default(Text("OK"))
)
}
}
.aspectRatio(1, contentMode: .fit)
.alert(isPresented: $showingLockedAlert) {
Alert(
title: Text("Icon Locked"),
message: Text(selectedLockedMessage),
dismissButton: .default(Text("OK"))
)
}
}
Button("Suggest new icon") {
openURL(IconConfigurationModel.suggestNewIconLink)
Expand All @@ -164,3 +135,59 @@ struct IconConfigurationView: View {
}
}
}

struct IconVew: View {
@ObservedObject var model: IconConfigurationModel
let icon: Icon

@State private var hasBeenUnlocked: Bool = false
@Default(.timesLooped) var timesLooped
@State private var nextUnlockCount: Int = -1
@State private var loopsLeft: Int = -1

var body: some View {
Group {
if hasBeenUnlocked {
Image(nsImage: NSImage(named: icon.iconName)!)
.resizable()
.aspectRatio(contentMode: .fit)
.padding(10)
} else {
VStack(alignment: .center) {
Spacer()
Image(._18PxLock)
Text(nextUnlockCount == icon.unlockTime ? "\(loopsLeft) \(loopsLeft > 1 ? "Loops left" : "Loop left")" : "Locked")
.font(.caption)
.foregroundColor(.secondary)
.contentTransition(.numericText())
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
model.selectedLockedMessage = model.lockedMessages.randomElement() ?? ""
model.showingLockedAlert = true
}
}
}
.onAppear {
hasBeenUnlocked = icon.selectable

if !hasBeenUnlocked {
nextUnlockCount = model.nextIconUnlockLoopCount(timesLooped: timesLooped)
loopsLeft = nextUnlockCount - timesLooped
}
}
.onChange(of: timesLooped) { _ in
withAnimation(.smooth(duration: 0.25)) {
hasBeenUnlocked = icon.selectable
}

if !hasBeenUnlocked {
withAnimation(.smooth(duration: 0.25)) {
nextUnlockCount = model.nextIconUnlockLoopCount(timesLooped: timesLooped)
loopsLeft = nextUnlockCount - timesLooped
}
}
}
}
}

0 comments on commit 64012be

Please sign in to comment.