can have / save multiple custom widgets
This commit is contained in:
@@ -293,8 +293,15 @@ struct SmallIconView: View {
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
CustomWidgetView(customWidgetModel: UserDefaultsStore.getCustomWidget())
|
||||
.frame(width: geo.size.width, height: geo.size.height)
|
||||
if let inUseWidget = UserDefaultsStore.getCustomWidgets().first(where: {
|
||||
$0.inUse == true
|
||||
}) {
|
||||
CustomWidgetView(customWidgetModel: inUseWidget)
|
||||
.frame(width: geo.size.width, height: geo.size.height)
|
||||
} else {
|
||||
CustomWidgetView(customWidgetModel: CustomWidgetModel.randomWidget)
|
||||
.frame(width: geo.size.width, height: geo.size.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,21 +82,124 @@ class UserDefaultsStore {
|
||||
}
|
||||
}
|
||||
|
||||
static func getCustomWidget() -> CustomWidgetModel {
|
||||
static func getCustomWidgets() -> [CustomWidgetModel] {
|
||||
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data,
|
||||
let model = try? JSONDecoder().decode(CustomWidgetModel.self, from: data) {
|
||||
let model = try? JSONDecoder().decode([CustomWidgetModel].self, from: data) {
|
||||
return model
|
||||
} else {
|
||||
return CustomWidgetModel.defaultCustomWidget
|
||||
GroupUserDefaults.groupDefaults.removeObject(forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
|
||||
let widget = CustomWidgetModel.randomWidget
|
||||
widget.isSaved = true
|
||||
let widgets = [widget]
|
||||
let data = try! JSONEncoder().encode(widgets)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
|
||||
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data,
|
||||
let models = try? JSONDecoder().decode([CustomWidgetModel].self, from: data) {
|
||||
let sorted = models.sorted(by: {
|
||||
$0.createdDate < $1.createdDate
|
||||
})
|
||||
return sorted
|
||||
} else {
|
||||
fatalError("error getting widgets")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func saveCustomWidget(widgetModel: CustomWidgetModel) -> CustomWidgetModel {
|
||||
static func saveCustomWidget(widgetModel: CustomWidgetModel, inUse: Bool) -> [CustomWidgetModel] {
|
||||
do {
|
||||
let data = try JSONEncoder().encode(widgetModel)
|
||||
var existingWidgets = getCustomWidgets()
|
||||
|
||||
if let exisitingWidget = existingWidgets.firstIndex(where: {
|
||||
$0.uuid == widgetModel.uuid
|
||||
}) {
|
||||
existingWidgets.remove(at: exisitingWidget)
|
||||
// give it differnet uuid so the view updates
|
||||
widgetModel.uuid = UUID().uuidString
|
||||
}
|
||||
|
||||
if inUse {
|
||||
existingWidgets.forEach({
|
||||
$0.inUse = false
|
||||
})
|
||||
|
||||
widgetModel.inUse = true
|
||||
}
|
||||
|
||||
existingWidgets.append(widgetModel)
|
||||
existingWidgets.forEach({
|
||||
$0.isSaved = true
|
||||
})
|
||||
let data = try JSONEncoder().encode(existingWidgets)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
return UserDefaultsStore.getCustomWidget()
|
||||
return UserDefaultsStore.getCustomWidgets()
|
||||
} catch {
|
||||
fatalError("error saving")
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func deleteCustomWidget(withUUID uuid: String) -> [CustomWidgetModel] {
|
||||
do {
|
||||
var existingWidgets = getCustomWidgets()
|
||||
|
||||
if let exisitingWidget = existingWidgets.firstIndex(where: {
|
||||
$0.uuid == uuid
|
||||
}) {
|
||||
existingWidgets.remove(at: exisitingWidget)
|
||||
}
|
||||
|
||||
if existingWidgets.count == 0 {
|
||||
let widget = CustomWidgetModel.randomWidget
|
||||
widget.isSaved = true
|
||||
widget.inUse = true
|
||||
existingWidgets.append(widget)
|
||||
}
|
||||
|
||||
if let _ = existingWidgets.first(where: {
|
||||
$0.inUse == true
|
||||
}) {} else {
|
||||
if let first = existingWidgets.first {
|
||||
first.inUse = true
|
||||
}
|
||||
}
|
||||
|
||||
let data = try JSONEncoder().encode(existingWidgets)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
return UserDefaultsStore.getCustomWidgets()
|
||||
} catch {
|
||||
fatalError("error saving")
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func makeWidgetCurrent(withUUID uuid: String) -> [CustomWidgetModel] {
|
||||
do {
|
||||
let existingWidgets = getCustomWidgets()
|
||||
|
||||
if let foundWidget = existingWidgets.first(where: {
|
||||
$0.uuid == uuid
|
||||
}) {
|
||||
existingWidgets.forEach({
|
||||
$0.inUse = false
|
||||
})
|
||||
|
||||
foundWidget.inUse = true
|
||||
} else {
|
||||
if let first = existingWidgets.first {
|
||||
first.inUse = true
|
||||
}
|
||||
}
|
||||
|
||||
existingWidgets.forEach({
|
||||
$0.isSaved = true
|
||||
})
|
||||
|
||||
let data = try JSONEncoder().encode(existingWidgets)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
return UserDefaultsStore.getCustomWidgets()
|
||||
} catch {
|
||||
fatalError("error saving")
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import SwiftUI
|
||||
|
||||
struct CreateWidgetView: View {
|
||||
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@StateObject var customWidget = UserDefaultsStore.getCustomWidget()
|
||||
@StateObject private var customWidget: CustomWidgetModel
|
||||
|
||||
static var iconViewBGs: [(CustomWidgetBackGroundOptions, UUID)] = {
|
||||
var blah = [(CustomWidgetBackGroundOptions, UUID)]()
|
||||
@@ -22,20 +23,26 @@ struct CreateWidgetView: View {
|
||||
|
||||
@State private var mouth: CustomWidgetMouthOptions = CustomWidgetMouthOptions.defaultOption
|
||||
|
||||
private var randomElements: [AnyView] = [
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)),
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)),
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)),
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20))
|
||||
]
|
||||
private var randomElements: [AnyView]
|
||||
|
||||
init(customWidget: CustomWidgetModel, randomElements: [AnyView] = [AnyView]()) {
|
||||
self.randomElements = [
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)),
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)),
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)),
|
||||
AnyView(Image(CustomWidgetBackGroundOptions.selectable.randomElement()!.rawValue)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20))
|
||||
]
|
||||
|
||||
_customWidget = StateObject(wrappedValue: customWidget)
|
||||
}
|
||||
|
||||
func update(eye: CustomWidgetEyes, eyeOption: CustomWidgetEyeOptions) {
|
||||
switch eye {
|
||||
@@ -90,7 +97,6 @@ struct CreateWidgetView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
widgetView
|
||||
// .frame(width: 256, height: 256)
|
||||
.cornerRadius(10)
|
||||
.padding()
|
||||
|
||||
@@ -249,9 +255,10 @@ struct CreateWidgetView: View {
|
||||
.background(.blue)
|
||||
|
||||
Button(action: {
|
||||
UserDefaultsStore.saveCustomWidget(widgetModel: customWidget)
|
||||
UserDefaultsStore.saveCustomWidget(widgetModel: customWidget, inUse: true)
|
||||
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
|
||||
impactMed.impactOccurred()
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("Save")
|
||||
.font(.title)
|
||||
@@ -261,6 +268,38 @@ struct CreateWidgetView: View {
|
||||
})
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.background(.green)
|
||||
|
||||
if customWidget.isSaved {
|
||||
Button(action: {
|
||||
UserDefaultsStore.deleteCustomWidget(withUUID: customWidget.uuid)
|
||||
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
|
||||
impactMed.impactOccurred()
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("Delete")
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(UIColor.white))
|
||||
|
||||
})
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.background(.orange)
|
||||
|
||||
Button(action: {
|
||||
UserDefaultsStore.makeWidgetCurrent(withUUID: customWidget.uuid)
|
||||
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
|
||||
impactMed.impactOccurred()
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("Use")
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(UIColor.white))
|
||||
|
||||
})
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.background(.pink)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,9 +309,9 @@ struct CreateWidgetView: View {
|
||||
struct CreateIconView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
CreateWidgetView()
|
||||
CreateWidgetView(customWidget: CustomWidgetModel.randomWidget)
|
||||
|
||||
CreateWidgetView()
|
||||
CreateWidgetView(customWidget: CustomWidgetModel.randomWidget)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,26 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class CustomWidgetModel: ObservableObject, Codable {
|
||||
class CustomWidgetModel: ObservableObject, Codable, NSCopying {
|
||||
static let numberOfBGItems = 109
|
||||
|
||||
static let defaultCustomWidget = CustomWidgetModel(leftEye: CustomWidgetEyeOptions.defaultOption,
|
||||
rightEye: CustomWidgetEyeOptions.defaultOption,
|
||||
mouth: CustomWidgetMouthOptions.defaultOption,
|
||||
background: CustomWidgetBackGroundOptions.defaultOption,
|
||||
bgColor: .red,
|
||||
innerColor: .green,
|
||||
bgOverlayColor: .orange,
|
||||
rightEyeColor: .orange,
|
||||
leftEyeColor: .yellow,
|
||||
mouthColor: .green,
|
||||
circleStrokeColor: .pink)
|
||||
|
||||
|
||||
static var randomWidget: CustomWidgetModel {
|
||||
return CustomWidgetModel(leftEye: CustomWidgetEyeOptions.defaultOption,
|
||||
rightEye: CustomWidgetEyeOptions.defaultOption,
|
||||
mouth: CustomWidgetMouthOptions.defaultOption,
|
||||
background: CustomWidgetBackGroundOptions.defaultOption,
|
||||
bgColor: Color.random(),
|
||||
innerColor: Color.random(),
|
||||
bgOverlayColor: Color.random(),
|
||||
rightEyeColor: Color.random(),
|
||||
leftEyeColor: Color.random(),
|
||||
mouthColor: Color.random(),
|
||||
circleStrokeColor: Color.random(),
|
||||
inUse: true,
|
||||
uuid: UUID().uuidString,
|
||||
isSaved: false,
|
||||
createdDate: Date())
|
||||
}
|
||||
|
||||
init(leftEye: CustomWidgetEyeOptions,
|
||||
rightEye: CustomWidgetEyeOptions,
|
||||
@@ -33,8 +38,11 @@ class CustomWidgetModel: ObservableObject, Codable {
|
||||
rightEyeColor: Color,
|
||||
leftEyeColor: Color,
|
||||
mouthColor: Color,
|
||||
circleStrokeColor: Color
|
||||
) {
|
||||
circleStrokeColor: Color,
|
||||
inUse: Bool,
|
||||
uuid: String,
|
||||
isSaved: Bool,
|
||||
createdDate: Date) {
|
||||
self.leftEye = leftEye
|
||||
self.rightEye = rightEye
|
||||
self.mouth = mouth
|
||||
@@ -46,6 +54,10 @@ class CustomWidgetModel: ObservableObject, Codable {
|
||||
self.leftEyeColor = leftEyeColor
|
||||
self.mouthColor = mouthColor
|
||||
self.circleStrokeColor = circleStrokeColor
|
||||
self.inUse = inUse
|
||||
self.uuid = uuid
|
||||
self.isSaved = isSaved
|
||||
self.createdDate = createdDate
|
||||
}
|
||||
|
||||
@Published var leftEye: CustomWidgetEyeOptions
|
||||
@@ -62,7 +74,11 @@ class CustomWidgetModel: ObservableObject, Codable {
|
||||
@Published var mouthColor: Color
|
||||
|
||||
@Published var circleStrokeColor: Color
|
||||
|
||||
@Published var inUse: Bool
|
||||
@Published var uuid: String
|
||||
@Published var isSaved: Bool
|
||||
@Published var createdDate: Date
|
||||
|
||||
public var backgroundImages : [(Image, String)] {
|
||||
if background == .random {
|
||||
var blah = [(Image, String)]()
|
||||
@@ -81,7 +97,7 @@ class CustomWidgetModel: ObservableObject, Codable {
|
||||
}
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
case leftEye, rightEye, mouth, background, bgColor, innerColor, bgOverlayColor, leftEyeColor, rightEyeColor, mouthColor, circleStrokeColor
|
||||
case leftEye, rightEye, mouth, background, bgColor, innerColor, bgOverlayColor, leftEyeColor, rightEyeColor, mouthColor, circleStrokeColor, inUse, uuid, isSaved, createdDate
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
@@ -98,6 +114,10 @@ class CustomWidgetModel: ObservableObject, Codable {
|
||||
rightEyeColor = try container.decode(Color.self, forKey: .rightEyeColor)
|
||||
mouthColor = try container.decode(Color.self, forKey: .mouthColor)
|
||||
circleStrokeColor = try container.decode(Color.self, forKey: .circleStrokeColor)
|
||||
inUse = try container.decode(Bool.self, forKey: .inUse)
|
||||
uuid = try container.decode(String.self, forKey: .uuid)
|
||||
isSaved = try container.decode(Bool.self, forKey: .isSaved)
|
||||
createdDate = try container.decode(Date.self, forKey: .createdDate)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
@@ -114,13 +134,29 @@ class CustomWidgetModel: ObservableObject, Codable {
|
||||
try container.encode(rightEyeColor, forKey: .rightEyeColor)
|
||||
try container.encode(mouthColor, forKey: .mouthColor)
|
||||
try container.encode(circleStrokeColor, forKey: .circleStrokeColor)
|
||||
try container.encode(inUse, forKey: .inUse)
|
||||
try container.encode(uuid, forKey: .uuid)
|
||||
try container.encode(isSaved, forKey: .isSaved)
|
||||
try container.encode(createdDate, forKey: .createdDate)
|
||||
}
|
||||
|
||||
func toData() -> Data {
|
||||
if let data = try? JSONEncoder().encode(self) {
|
||||
return data
|
||||
}
|
||||
return Data()
|
||||
func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = CustomWidgetModel(leftEye: self.leftEye,
|
||||
rightEye: self.rightEye,
|
||||
mouth: self.mouth,
|
||||
background: self.background,
|
||||
bgColor: self.bgColor,
|
||||
innerColor: self.innerColor,
|
||||
bgOverlayColor: self.bgOverlayColor,
|
||||
rightEyeColor: self.rightEyeColor,
|
||||
leftEyeColor: self.leftEyeColor,
|
||||
mouthColor: self.mouthColor,
|
||||
circleStrokeColor: self.circleStrokeColor,
|
||||
inUse: self.inUse,
|
||||
uuid: self.uuid,
|
||||
isSaved: self.isSaved,
|
||||
createdDate: self.createdDate)
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ struct CustomWidgetView: View {
|
||||
|
||||
struct WidgetView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CustomWidgetView(customWidgetModel: CustomWidgetModel.defaultCustomWidget)
|
||||
CustomWidgetView(customWidgetModel: CustomWidgetModel.randomWidget)
|
||||
.frame(width: 256, height: 256, alignment: .center)
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,12 @@ struct CustomizeView: View {
|
||||
@AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default
|
||||
@AppStorage(UserDefaultsStore.Keys.personalityPack.rawValue, store: GroupUserDefaults.groupDefaults) private var personalityPack: PersonalityPack = .Default
|
||||
|
||||
@State private var showCreateCustomWidget = false
|
||||
class StupidAssCustomWidgetObservableObject: ObservableObject {
|
||||
@Published var fuckingWrapped: CustomWidgetModel? = nil
|
||||
@Published var showFuckingSheet = false
|
||||
}
|
||||
|
||||
@StateObject private var selectedWidget = StupidAssCustomWidgetObservableObject()
|
||||
|
||||
let iconSets: [(String,String)] = [
|
||||
("PurpleFeelsAppIcon", "PurpleAppIcon"),
|
||||
@@ -38,8 +43,10 @@ struct CustomizeView: View {
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.sheet(isPresented: $showCreateCustomWidget) {
|
||||
CreateWidgetView()
|
||||
.sheet(isPresented: $selectedWidget.showFuckingSheet) {
|
||||
if let fuckingWrapped = selectedWidget.fuckingWrapped {
|
||||
CreateWidgetView(customWidget: fuckingWrapped)
|
||||
}
|
||||
}
|
||||
.background(
|
||||
theme.currentTheme.bg
|
||||
@@ -52,8 +59,10 @@ struct CustomizeView: View {
|
||||
theme.currentTheme.secondaryBGColor
|
||||
VStack {
|
||||
Text(String(localized: "settings_view_change_icon"))
|
||||
.font(.body)
|
||||
.foregroundColor(theme.currentTheme.labelColor)
|
||||
|
||||
HStack {
|
||||
|
||||
Button(action: {
|
||||
UIApplication.shared.setAlternateIconName(nil)
|
||||
}, label: {
|
||||
@@ -62,8 +71,7 @@ struct CustomizeView: View {
|
||||
.frame(width: 50, height:50)
|
||||
.cornerRadius(10)
|
||||
})
|
||||
.padding()
|
||||
|
||||
|
||||
ForEach(iconSets, id: \.self.0){ iconSet in
|
||||
Button(action: {
|
||||
UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in
|
||||
@@ -75,11 +83,10 @@ struct CustomizeView: View {
|
||||
.frame(width: 50, height:50)
|
||||
.cornerRadius(10)
|
||||
})
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
|
||||
@@ -90,6 +97,9 @@ struct CustomizeView: View {
|
||||
theme.currentTheme.secondaryBGColor
|
||||
VStack {
|
||||
Text(String(localized: "settings_background_title"))
|
||||
.font(.body)
|
||||
.foregroundColor(theme.currentTheme.labelColor)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
ForEach(Theme.allCases, id:\.rawValue) { aTheme in
|
||||
@@ -119,12 +129,30 @@ struct CustomizeView: View {
|
||||
private var createCustomWidget: some View {
|
||||
ZStack {
|
||||
theme.currentTheme.secondaryBGColor
|
||||
Button(action: {
|
||||
showCreateCustomWidget = true
|
||||
}, label: {
|
||||
VStack {
|
||||
Text("Create Custom Widget")
|
||||
})
|
||||
.padding()
|
||||
.font(.body)
|
||||
.foregroundColor(theme.currentTheme.labelColor)
|
||||
.onTapGesture {
|
||||
selectedWidget.fuckingWrapped = CustomWidgetModel.randomWidget
|
||||
selectedWidget.showFuckingSheet = true
|
||||
}
|
||||
.padding()
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(UserDefaultsStore.getCustomWidgets(), id: \.uuid) { widget in
|
||||
CustomWidgetView(customWidgetModel: widget)
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(10)
|
||||
.onTapGesture {
|
||||
selectedWidget.fuckingWrapped = widget.copy() as? CustomWidgetModel
|
||||
selectedWidget.showFuckingSheet = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
|
||||
@@ -201,8 +229,7 @@ struct CustomizeView: View {
|
||||
ForEach(PersonalityPack.allCases, id: \.self) { aPack in
|
||||
VStack(spacing: 10) {
|
||||
Text(String(aPack.title()))
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
.font(.body)
|
||||
.foregroundColor(theme.currentTheme.labelColor)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user