Files
Reflect/Shared/Views/CustomIcon/CreateWidgetView.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

365 lines
14 KiB
Swift

//
// CreateIconView.swift
// Reflect (iOS)
//
// Created by Trey Tartt on 2/13/22.
//
import SwiftUI
struct CreateWidgetView: View {
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@Environment(\.dismiss) var dismiss
private var textColor: Color { theme.currentTheme.labelColor }
@StateObject private var customWidget: CustomWidgetModel
@State private var mouth: CustomWidgetMouthOptions = CustomWidgetMouthOptions.defaultOption
@State private var showRightEyeImagePicker: Bool = false
@State private var showLeftEyeImagePicker: Bool = false
@State private var showMuthImagePicker: Bool = false
var widgetView: CustomWidgetView
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)
widgetView = CustomWidgetView(customWidgetModel: customWidget)
}
func update(eye: CustomWidgetEyes, eyeOption: CustomWidgeImageOptions) {
AnalyticsManager.shared.track(.widgetEyeUpdated(style: eyeOption.rawValue))
switch eye {
case .left:
customWidget.leftEye = eyeOption
case .right:
customWidget.rightEye = eyeOption
}
}
func createRandom() {
AnalyticsManager.shared.track(.widgetRandomized)
customWidget.bgColor = Color.random()
customWidget.innerColor = Color.random()
customWidget.bgOverlayColor = Color.random()
customWidget.circleStrokeColor = Color.random()
customWidget.leftEyeColor = Color.random()
customWidget.rightEyeColor = Color.random()
customWidget.mouthColor = Color.random()
update(eye: .left, eyeOption: CustomWidgeImageOptions.allCases.randomElement()!)
update(eye: .right, eyeOption: CustomWidgeImageOptions.allCases.randomElement()!)
update(mouthOption: CustomWidgeImageOptions.allCases.randomElement()!)
update(background: CustomWidgetBackGroundOptions.allCases.randomElement()!)
}
func update(mouthOption: CustomWidgeImageOptions) {
AnalyticsManager.shared.track(.widgetMouthUpdated(style: mouthOption.rawValue))
customWidget.mouth = mouthOption
}
func update(background: CustomWidgetBackGroundOptions) {
AnalyticsManager.shared.track(.widgetBackgroundUpdated(style: background.rawValue))
customWidget.background = background
}
var mixBG: some View {
VStack {
HStack {
randomElements[0]
randomElements[1]
}
HStack {
randomElements[2]
randomElements[3]
}
}
}
var bottomBarButtons: some View {
HStack(alignment: .center, spacing: 0) {
Button(action: {
AnalyticsManager.shared.track(.widgetShuffled)
createRandom()
}, label: {
Image(systemName: "shuffle")
.font(.title)
.foregroundColor(.white)
.padding([.top, .bottom])
})
.frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity)
.background(.blue)
Button(action: {
AnalyticsManager.shared.track(.widgetCreated)
UserDefaultsStore.saveCustomWidget(widgetModel: customWidget, inUse: false)
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
dismiss()
}, label: {
Text(String(localized: "create_widget_save"))
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
.padding([.top, .bottom])
})
.frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity)
.background(.green)
Button(action: {
AnalyticsManager.shared.track(.widgetUsed)
UserDefaultsStore.saveCustomWidget(widgetModel: customWidget, inUse: true)
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
dismiss()
}, label: {
Text(String(localized: "create_widget_use"))
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
.padding([.top, .bottom])
})
.frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity)
.background(.pink)
if customWidget.isSaved {
Button(action: {
AnalyticsManager.shared.track(.widgetDeleted)
UserDefaultsStore.deleteCustomWidget(withUUID: customWidget.uuid)
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
dismiss()
}, label: {
Image(systemName: "trash")
.font(.title)
.foregroundColor(.white)
.padding([.top, .bottom])
})
.frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: 40, maxHeight: .infinity)
.background(.orange)
}
}
.frame(minHeight: 40, maxHeight: .infinity)
}
var colorOptions: some View {
VStack {
HStack(spacing: 0) {
VStack(alignment: .center) {
Text(String(localized: "create_widget_background_color"))
ColorPicker("", selection: $customWidget.bgColor)
.onChange(of: customWidget.bgColor) {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "background"))
}
.labelsHidden()
}
.frame(minWidth: 0, maxWidth: .infinity)
VStack(alignment: .center) {
Text(String(localized: "create_widget_inner_color"))
ColorPicker("", selection: $customWidget.innerColor)
.onChange(of: customWidget.innerColor) {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "inner"))
}
.labelsHidden()
}
.frame(minWidth: 0, maxWidth: .infinity)
VStack(alignment: .center) {
Text(String(localized: "create_widget_face_outline_color"))
ColorPicker("", selection: $customWidget.circleStrokeColor)
.onChange(of: customWidget.circleStrokeColor) {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "outline"))
}
.labelsHidden()
}
.frame(minWidth: 0, maxWidth: .infinity)
}
HStack(spacing: 0) {
VStack(alignment: .center) {
Text(String(localized: "create_widget_view_left_eye_color"))
ColorPicker("", selection: $customWidget.leftEyeColor)
.onChange(of: customWidget.leftEyeColor) {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "left_eye"))
}
.labelsHidden()
}
.frame(minWidth: 0, maxWidth: .infinity)
VStack(alignment: .center) {
Text(String(localized: "create_widget_view_right_eye_color"))
ColorPicker("", selection: $customWidget.rightEyeColor)
.onChange(of: customWidget.rightEyeColor) {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "right_eye"))
}
.labelsHidden()
}
.frame(minWidth: 0, maxWidth: .infinity)
VStack(alignment: .center) {
Text(String(localized: "create_widget_view_mouth_color"))
ColorPicker("", selection: $customWidget.mouthColor)
.onChange(of: customWidget.mouthColor) {
AnalyticsManager.shared.track(.widgetColorUpdated(part: "mouth"))
}
.labelsHidden()
}
.frame(minWidth: 0, maxWidth: .infinity)
}
}
.multilineTextAlignment(.center)
.padding()
.background(
theme.currentTheme.secondaryBGColor
)
}
var bgImageOptions: some View {
HStack {
ForEach(CustomWidgetBackGroundOptions.selectable, id: \.self) { bg in
Image(bg.rawValue, bundle: .main)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 10, idealWidth: 40, maxWidth: 40,
minHeight: 10, idealHeight: 40, maxHeight: 40,
alignment: .center)
.onTapGesture {
update(background: bg)
}
}
mixBG
.onTapGesture {
update(background: .random)
}
Divider()
ColorPicker("", selection: $customWidget.bgOverlayColor)
}
.padding()
.background(
theme.currentTheme.secondaryBGColor
)
}
var faceImageOptions: some View {
HStack(alignment: .center) {
Text(String(localized: "create_widget_view_left_eye"))
.onTapGesture(perform: {
showLeftEyeImagePicker.toggle()
})
.foregroundColor(textColor)
.foregroundColor(textColor)
.frame(minWidth: 0, maxWidth: .infinity)
Divider()
Text(String(localized: "create_widget_view_right_eye"))
.onTapGesture(perform: {
showRightEyeImagePicker.toggle()
})
.foregroundColor(textColor)
.frame(minWidth: 0, maxWidth: .infinity)
Divider()
Text(String(localized: "create_widget_view_mouth"))
.onTapGesture(perform: {
showMuthImagePicker.toggle()
})
.foregroundColor(textColor)
.foregroundColor(textColor)
.frame(minWidth: 0, maxWidth: .infinity)
}
.padding()
.background(
theme.currentTheme.secondaryBGColor
)
}
var body: some View {
GeometryReader { geo in
VStack(spacing: 0) {
widgetView
.cornerRadius(10)
.padding()
.frame(width: geo.size.width, height: geo.size.width)
Spacer()
ScrollView {
VStack(spacing: 0) {
Divider().background(Color(UIColor.tertiarySystemBackground))
faceImageOptions
Divider().background(Color(UIColor.tertiarySystemBackground))
bgImageOptions
Divider().background(Color(UIColor.tertiarySystemBackground))
colorOptions
Divider().background(Color(UIColor.tertiarySystemBackground))
bottomBarButtons
}
}
}
}
.sheet(isPresented: $showRightEyeImagePicker) {
ImagePickerGridView(pickedImageClosure: { image in
update(eye: .right, eyeOption: image)
})
}
.sheet(isPresented: $showLeftEyeImagePicker) {
ImagePickerGridView(pickedImageClosure: { image in
update(eye: .left, eyeOption: image)
})
}
.sheet(isPresented: $showMuthImagePicker) {
ImagePickerGridView(pickedImageClosure: { image in
update(mouthOption: image)
})
}
}
}
struct CreateIconView_Previews: PreviewProvider {
static var widget: CustomWidgetModel {
let _widget = CustomWidgetModel.randomWidget
_widget.isSaved = true
return _widget
}
static var previews: some View {
Group {
CreateWidgetView(customWidget: CreateIconView_Previews.widget)
CreateWidgetView(customWidget: CustomWidgetModel.randomWidget)
.preferredColorScheme(.dark)
}
}
}