diff --git a/FeelsWidget/FeelsWidget.swift b/FeelsWidget/FeelsWidget.swift index 260510f..4fa7935 100644 --- a/FeelsWidget/FeelsWidget.swift +++ b/FeelsWidget/FeelsWidget.swift @@ -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) + } } } } diff --git a/Shared/Models/UserDefaultsStore.swift b/Shared/Models/UserDefaultsStore.swift index 6eaf9d3..6b74042 100644 --- a/Shared/Models/UserDefaultsStore.swift +++ b/Shared/Models/UserDefaultsStore.swift @@ -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") } diff --git a/Shared/views/CustomIcon/CreateWidgetView.swift b/Shared/views/CustomIcon/CreateWidgetView.swift index 85e27bf..acf77ea 100644 --- a/Shared/views/CustomIcon/CreateWidgetView.swift +++ b/Shared/views/CustomIcon/CreateWidgetView.swift @@ -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) } } diff --git a/Shared/views/CustomWidget/CustomWidgetModel.swift b/Shared/views/CustomWidget/CustomWidgetModel.swift index e4e0ba2..77f2b11 100644 --- a/Shared/views/CustomWidget/CustomWidgetModel.swift +++ b/Shared/views/CustomWidget/CustomWidgetModel.swift @@ -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 } } diff --git a/Shared/views/CustomWidget/CustomWidgetView.swift b/Shared/views/CustomWidget/CustomWidgetView.swift index 481b56c..0547b1d 100644 --- a/Shared/views/CustomWidget/CustomWidgetView.swift +++ b/Shared/views/CustomWidget/CustomWidgetView.swift @@ -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) } diff --git a/Shared/views/CustomizeView/CustomizeView.swift b/Shared/views/CustomizeView/CustomizeView.swift index 94fca3d..573abc4 100644 --- a/Shared/views/CustomizeView/CustomizeView.swift +++ b/Shared/views/CustomizeView/CustomizeView.swift @@ -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)