Compare commits
No commits in common. "main" and "ac3031dd0599762896a97080350fbc4c1b58cd7b" have entirely different histories.
main
...
ac3031dd05
@ -17,10 +17,6 @@
|
|||||||
248D14F029A230FE00AE4C0D /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248D14EF29A230FE00AE4C0D /* Constants.swift */; };
|
248D14F029A230FE00AE4C0D /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248D14EF29A230FE00AE4C0D /* Constants.swift */; };
|
||||||
248D14F329A2435000AE4C0D /* UtilityViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248D14F129A2435000AE4C0D /* UtilityViews.swift */; };
|
248D14F329A2435000AE4C0D /* UtilityViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248D14F129A2435000AE4C0D /* UtilityViews.swift */; };
|
||||||
248D14F629A243F200AE4C0D /* UtilityExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248D14F529A243F200AE4C0D /* UtilityExtensions.swift */; };
|
248D14F629A243F200AE4C0D /* UtilityExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248D14F529A243F200AE4C0D /* UtilityExtensions.swift */; };
|
||||||
24D4D3A729A9E02F0064E566 /* PaletteManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D4D3A629A9E02F0064E566 /* PaletteManager.swift */; };
|
|
||||||
24E5FE8929A7AB4000794732 /* PaletteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E5FE8829A7AB4000794732 /* PaletteStore.swift */; };
|
|
||||||
24E5FE8B29A9944600794732 /* PaletteChooser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E5FE8A29A9944600794732 /* PaletteChooser.swift */; };
|
|
||||||
24E5FE8D29A9B25800794732 /* PaletteEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E5FE8C29A9B25800794732 /* PaletteEditor.swift */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@ -36,10 +32,6 @@
|
|||||||
248D14EF29A230FE00AE4C0D /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
248D14EF29A230FE00AE4C0D /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||||
248D14F129A2435000AE4C0D /* UtilityViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityViews.swift; sourceTree = "<group>"; };
|
248D14F129A2435000AE4C0D /* UtilityViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityViews.swift; sourceTree = "<group>"; };
|
||||||
248D14F529A243F200AE4C0D /* UtilityExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityExtensions.swift; sourceTree = "<group>"; };
|
248D14F529A243F200AE4C0D /* UtilityExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityExtensions.swift; sourceTree = "<group>"; };
|
||||||
24D4D3A629A9E02F0064E566 /* PaletteManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteManager.swift; sourceTree = "<group>"; };
|
|
||||||
24E5FE8829A7AB4000794732 /* PaletteStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteStore.swift; sourceTree = "<group>"; };
|
|
||||||
24E5FE8A29A9944600794732 /* PaletteChooser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteChooser.swift; sourceTree = "<group>"; };
|
|
||||||
24E5FE8C29A9B25800794732 /* PaletteEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteEditor.swift; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -72,15 +64,11 @@
|
|||||||
24145E7A29A1489700ECB9D1 /* EmojiArt */ = {
|
24145E7A29A1489700ECB9D1 /* EmojiArt */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
24D4D3A629A9E02F0064E566 /* PaletteManager.swift */,
|
|
||||||
248D14EF29A230FE00AE4C0D /* Constants.swift */,
|
248D14EF29A230FE00AE4C0D /* Constants.swift */,
|
||||||
24145E7B29A1489700ECB9D1 /* EmojiArtApp.swift */,
|
24145E7B29A1489700ECB9D1 /* EmojiArtApp.swift */,
|
||||||
24145E7D29A1489700ECB9D1 /* EmojiArtDocumentView.swift */,
|
24145E7D29A1489700ECB9D1 /* EmojiArtDocumentView.swift */,
|
||||||
248D14F129A2435000AE4C0D /* UtilityViews.swift */,
|
248D14F129A2435000AE4C0D /* UtilityViews.swift */,
|
||||||
24145E8A29A1498500ECB9D1 /* EmojiArtModel.swift */,
|
24145E8A29A1498500ECB9D1 /* EmojiArtModel.swift */,
|
||||||
24E5FE8829A7AB4000794732 /* PaletteStore.swift */,
|
|
||||||
24E5FE8A29A9944600794732 /* PaletteChooser.swift */,
|
|
||||||
24E5FE8C29A9B25800794732 /* PaletteEditor.swift */,
|
|
||||||
248D14F529A243F200AE4C0D /* UtilityExtensions.swift */,
|
248D14F529A243F200AE4C0D /* UtilityExtensions.swift */,
|
||||||
248D14EB29A227C700AE4C0D /* EmojiArtDocument.swift */,
|
248D14EB29A227C700AE4C0D /* EmojiArtDocument.swift */,
|
||||||
24145E8C29A225E900ECB9D1 /* EmojiArtModel.Background.swift */,
|
24145E8C29A225E900ECB9D1 /* EmojiArtModel.Background.swift */,
|
||||||
@ -173,14 +161,10 @@
|
|||||||
24145E7C29A1489700ECB9D1 /* EmojiArtApp.swift in Sources */,
|
24145E7C29A1489700ECB9D1 /* EmojiArtApp.swift in Sources */,
|
||||||
248D14EC29A227C700AE4C0D /* EmojiArtDocument.swift in Sources */,
|
248D14EC29A227C700AE4C0D /* EmojiArtDocument.swift in Sources */,
|
||||||
248D14F029A230FE00AE4C0D /* Constants.swift in Sources */,
|
248D14F029A230FE00AE4C0D /* Constants.swift in Sources */,
|
||||||
24E5FE8929A7AB4000794732 /* PaletteStore.swift in Sources */,
|
|
||||||
24D4D3A729A9E02F0064E566 /* PaletteManager.swift in Sources */,
|
|
||||||
24145E8B29A1498500ECB9D1 /* EmojiArtModel.swift in Sources */,
|
24145E8B29A1498500ECB9D1 /* EmojiArtModel.swift in Sources */,
|
||||||
24E5FE8D29A9B25800794732 /* PaletteEditor.swift in Sources */,
|
|
||||||
24145E8D29A225E900ECB9D1 /* EmojiArtModel.Background.swift in Sources */,
|
24145E8D29A225E900ECB9D1 /* EmojiArtModel.Background.swift in Sources */,
|
||||||
248D14F629A243F200AE4C0D /* UtilityExtensions.swift in Sources */,
|
248D14F629A243F200AE4C0D /* UtilityExtensions.swift in Sources */,
|
||||||
248D14F329A2435000AE4C0D /* UtilityViews.swift in Sources */,
|
248D14F329A2435000AE4C0D /* UtilityViews.swift in Sources */,
|
||||||
24E5FE8B29A9944600794732 /* PaletteChooser.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Bucket
|
|
||||||
uuid = "E709882D-ACBF-4819-A5E7-499B9D69B8A9"
|
|
||||||
type = "1"
|
|
||||||
version = "2.0">
|
|
||||||
</Bucket>
|
|
||||||
@ -8,9 +8,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
struct const {
|
struct const {
|
||||||
static let defaultEmojiFontSize: CGFloat = 40
|
static let defaultEmojiFontSize: CGFloat = 40
|
||||||
static let coalescingInterval = 5.0
|
static let coalescingInterval = 5.0
|
||||||
static let paletteEditorMinWidth: CGFloat = 400
|
|
||||||
static let paletteEditorMinHeight: CGFloat = 400
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,14 +9,10 @@ import SwiftUI
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct EmojiArtApp: App {
|
struct EmojiArtApp: App {
|
||||||
@StateObject var document = EmojiArtDocument()
|
let document = EmojiArtDocument()
|
||||||
@StateObject var paletteStore = PaletteStore(named: "Default")
|
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
EmojiArtDocumentView(document: document)
|
EmojiArtDocumentView(document: document)
|
||||||
.environmentObject(paletteStore)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -66,10 +66,9 @@ class EmojiArtDocument: ObservableObject {
|
|||||||
@Published var backgroundImage: UIImage?
|
@Published var backgroundImage: UIImage?
|
||||||
@Published var backgroundImageFetchStatus = BackgroundImageFetchStatus.idle
|
@Published var backgroundImageFetchStatus = BackgroundImageFetchStatus.idle
|
||||||
|
|
||||||
enum BackgroundImageFetchStatus: Equatable {
|
enum BackgroundImageFetchStatus {
|
||||||
case idle
|
case idle
|
||||||
case fetching
|
case fetching
|
||||||
case failed(URL )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Intent(s)
|
// MARK: - Intent(s)
|
||||||
@ -110,9 +109,6 @@ class EmojiArtDocument: ObservableObject {
|
|||||||
if imageData != nil {
|
if imageData != nil {
|
||||||
self?.backgroundImage = UIImage(data: imageData!)
|
self?.backgroundImage = UIImage(data: imageData!)
|
||||||
}
|
}
|
||||||
if self?.backgroundImage == nil {
|
|
||||||
self?.backgroundImageFetchStatus = .failed(url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,12 +8,14 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EmojiArtDocumentView: View {
|
struct EmojiArtDocumentView: View {
|
||||||
|
let testEmojis = "😀😃😄😁😆😅😂🤣🥲🥹☺️😊😇🙂🙃😉😌😍🥰😘😗😙😚😋😛😝😜🤪🤨🧐🤓😎🥸🤩"
|
||||||
|
|
||||||
@ObservedObject var document: EmojiArtDocument
|
@ObservedObject var document: EmojiArtDocument
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
documentBody
|
documentBody
|
||||||
PaletteChooser(emojiFontSize: const.defaultEmojiFontSize)
|
palette
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,31 +44,9 @@ struct EmojiArtDocumentView: View {
|
|||||||
drop(providers: providers, at: location, in: geometry)
|
drop(providers: providers, at: location, in: geometry)
|
||||||
}
|
}
|
||||||
.gesture(panGesture().simultaneously(with: zoomGesture()))
|
.gesture(panGesture().simultaneously(with: zoomGesture()))
|
||||||
.alert(item: $alertToShow) { alertToShow in
|
|
||||||
alertToShow.alert()
|
|
||||||
}
|
|
||||||
.onChange(of: document.backgroundImageFetchStatus) { status in
|
|
||||||
switch status {
|
|
||||||
case .failed(let url):
|
|
||||||
showBackgroundImageFetchFailedAlert(url)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@State private var alertToShow: IdentifiableAlert?
|
|
||||||
|
|
||||||
private func showBackgroundImageFetchFailedAlert(_ url: URL) {
|
|
||||||
alertToShow = IdentifiableAlert(id: "Fetch failed: " + url.absoluteString, alert: {
|
|
||||||
Alert(
|
|
||||||
title: Text("Background Image Fetch"),
|
|
||||||
message: Text("Couldn't load image from \(url)."),
|
|
||||||
dismissButton: .default(Text("OK")))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private func drop(providers: [NSItemProvider], at location: CGPoint, in geometry: GeometryProxy) -> Bool {
|
private func drop(providers: [NSItemProvider], at location: CGPoint, in geometry: GeometryProxy) -> Bool {
|
||||||
var found = providers.loadObjects(ofType: URL.self) { url in
|
var found = providers.loadObjects(ofType: URL.self) { url in
|
||||||
document.setBackground(EmojiArtModel.Background.url(url.imageURL))
|
document.setBackground(EmojiArtModel.Background.url(url.imageURL))
|
||||||
@ -163,6 +143,25 @@ struct EmojiArtDocumentView: View {
|
|||||||
steadyStateZoomScale = min(hZoom, vZoom)
|
steadyStateZoomScale = min(hZoom, vZoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var palette: some View {
|
||||||
|
SrcollingEmojiView(emojis: testEmojis)
|
||||||
|
.font(.system(size: const.defaultEmojiFontSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SrcollingEmojiView: View {
|
||||||
|
let emojis: String
|
||||||
|
var body: some View {
|
||||||
|
ScrollView(.horizontal) {
|
||||||
|
HStack {
|
||||||
|
ForEach(emojis.map { String($0) }, id: \.self) { emoji in
|
||||||
|
Text(emoji)
|
||||||
|
.onDrag { NSItemProvider(object: emoji as NSString) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
|
|||||||
@ -1,111 +0,0 @@
|
|||||||
//
|
|
||||||
// PaletteChooser.swift
|
|
||||||
// EmojiArt
|
|
||||||
//
|
|
||||||
// Created by ching on 2023/2/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct PaletteChooser: View {
|
|
||||||
var emojiFontSize: CGFloat = const.defaultEmojiFontSize
|
|
||||||
var emojiFont: Font { .system(size: emojiFontSize) }
|
|
||||||
|
|
||||||
@EnvironmentObject var store: PaletteStore
|
|
||||||
@State private var chosenPaletteIndex = 0
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
paletteButton
|
|
||||||
body(for: store.palette(at: chosenPaletteIndex)
|
|
||||||
)
|
|
||||||
}.clipped()
|
|
||||||
}
|
|
||||||
|
|
||||||
var paletteButton: some View {
|
|
||||||
Button {
|
|
||||||
withAnimation {
|
|
||||||
chosenPaletteIndex = (chosenPaletteIndex + 1) % store.palettes.count
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "paintpalette")
|
|
||||||
}
|
|
||||||
.font(emojiFont)
|
|
||||||
.contextMenu { contextMenu }
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
var contextMenu: some View {
|
|
||||||
AnimatedActionButton(title: "Edit", systemImage: "pencil") {
|
|
||||||
editing = true
|
|
||||||
}
|
|
||||||
AnimatedActionButton(title: "New", systemImage: "plus") {
|
|
||||||
store.insertPalette(named: "New", emojis: "", at: chosenPaletteIndex)
|
|
||||||
editing = true
|
|
||||||
}
|
|
||||||
AnimatedActionButton(title: "Delete", systemImage: "minus.circle") {
|
|
||||||
chosenPaletteIndex = store.removePalette(at: chosenPaletteIndex)
|
|
||||||
}
|
|
||||||
AnimatedActionButton(title: "Manager", systemImage: "slider.vertical.3") {
|
|
||||||
managing = true
|
|
||||||
}
|
|
||||||
gotoMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
var gotoMenu: some View {
|
|
||||||
Menu {
|
|
||||||
ForEach(store.palettes) {
|
|
||||||
palette in AnimatedActionButton(title: palette.name) {
|
|
||||||
if let index = store.palettes.index(matching: palette) {
|
|
||||||
chosenPaletteIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Label("Go To", systemImage: "text.insert")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func body(for palette: Palette) -> some View {
|
|
||||||
HStack {
|
|
||||||
Text(palette.name)
|
|
||||||
SrcollingEmojiView(emojis: palette.emojis)
|
|
||||||
.font(emojiFont)
|
|
||||||
}
|
|
||||||
.id(palette.id)
|
|
||||||
.transition(rollTransaction)
|
|
||||||
.popover(isPresented: $editing) {
|
|
||||||
PaletteEditor(palette: $store.palettes[chosenPaletteIndex])
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $managing) {
|
|
||||||
PaletteManager()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@State private var editing = false
|
|
||||||
@State private var managing = false
|
|
||||||
|
|
||||||
var rollTransaction: AnyTransition {
|
|
||||||
AnyTransition.asymmetric(insertion: .offset(x: 0, y: emojiFontSize), removal: .offset(x: 0, y: -emojiFontSize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SrcollingEmojiView: View {
|
|
||||||
let emojis: String
|
|
||||||
var body: some View {
|
|
||||||
ScrollView(.horizontal) {
|
|
||||||
HStack {
|
|
||||||
ForEach(emojis.map { String($0) }, id: \.self) { emoji in
|
|
||||||
Text(emoji)
|
|
||||||
.onDrag { NSItemProvider(object: emoji as NSString) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PaletteChooser_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
PaletteChooser()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
//
|
|
||||||
// PaletteEditor.swift
|
|
||||||
// EmojiArt
|
|
||||||
//
|
|
||||||
// Created by ching on 2023/2/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct PaletteEditor: View {
|
|
||||||
@Binding var palette: Palette
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Form {
|
|
||||||
nameSection
|
|
||||||
addEmojiSection
|
|
||||||
removeEmojiSection
|
|
||||||
}
|
|
||||||
.frame(minWidth: const.paletteEditorMinWidth, minHeight: const.paletteEditorMinHeight)
|
|
||||||
.navigationTitle("Edit \(palette.name)")
|
|
||||||
}
|
|
||||||
|
|
||||||
var nameSection: some View {
|
|
||||||
Section(header: Text("Name")) {
|
|
||||||
TextField("Name", text: $palette.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@State private var emojiToAdd = ""
|
|
||||||
|
|
||||||
var addEmojiSection: some View {
|
|
||||||
Section(header: Text("Add Emoji")) {
|
|
||||||
TextField("", text: $emojiToAdd)
|
|
||||||
.onChange(of: emojiToAdd) { emojis in
|
|
||||||
addEmojis(emojis)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addEmojis(_ emojis: String) {
|
|
||||||
palette.emojis = (emojis + palette.emojis)
|
|
||||||
.filter { $0.isEmoji }
|
|
||||||
}
|
|
||||||
|
|
||||||
var removeEmojiSection: some View {
|
|
||||||
Section(header: Text("Remove Emoji")) {
|
|
||||||
let emojis = palette.emojis.map { String($0) }
|
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: const.defaultEmojiFontSize))]) {
|
|
||||||
ForEach(emojis, id: \.self) { emoji in
|
|
||||||
Text(emoji)
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation {
|
|
||||||
palette.emojis.removeAll(where: { String($0) == emoji })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PaletteEditor_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
PaletteEditor(palette: .constant(PaletteStore(named: "Priview").palette(at: 1)))
|
|
||||||
.previewLayout(.fixed(width: 350, height: 350))
|
|
||||||
PaletteEditor(palette: .constant(PaletteStore(named: "Priview").palette(at: 1)))
|
|
||||||
.previewLayout(.fixed(width: 350, height: 650))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
//
|
|
||||||
// PaletteManager.swift
|
|
||||||
// EmojiArt
|
|
||||||
//
|
|
||||||
// Created by ching on 2023/2/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct PaletteManager: View {
|
|
||||||
@EnvironmentObject var store: PaletteStore
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
|
||||||
@State private var editMode: EditMode = .inactive
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
List {
|
|
||||||
ForEach(store.palettes) { palette in
|
|
||||||
NavigationLink(destination: PaletteEditor(palette: $store.palettes[palette])) {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text(palette.name)
|
|
||||||
Text(palette.emojis)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onDelete { indexSet in
|
|
||||||
store.palettes.remove(atOffsets: indexSet)
|
|
||||||
}
|
|
||||||
.onMove { indexSet, newOffset in
|
|
||||||
store.palettes.move(fromOffsets: indexSet, toOffset: newOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("Manage Palettes")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.toolbar {
|
|
||||||
EditButton()
|
|
||||||
}
|
|
||||||
.environment(\.editMode, $editMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PaletteManager_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
PaletteManager()
|
|
||||||
.previewDevice("iPhone 14 Pro Max")
|
|
||||||
.environmentObject(PaletteStore(named: "preview"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
//
|
|
||||||
// PaletteStore.swift
|
|
||||||
// EmojiArt
|
|
||||||
//
|
|
||||||
// Created by ching on 2023/2/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct Palette: Identifiable, Codable, Hashable {
|
|
||||||
var name: String
|
|
||||||
var emojis: String
|
|
||||||
var id: Int
|
|
||||||
|
|
||||||
fileprivate init(name: String, emojis: String, id: Int) {
|
|
||||||
self.name = name
|
|
||||||
self.emojis = emojis
|
|
||||||
self.id = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PaletteStore: ObservableObject {
|
|
||||||
let name: String
|
|
||||||
|
|
||||||
@Published var palettes = [Palette]() {
|
|
||||||
didSet {
|
|
||||||
storeInUserDefaults()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var userDefaultsKey: String {
|
|
||||||
"PaletteStore:" + name
|
|
||||||
}
|
|
||||||
|
|
||||||
private func storeInUserDefaults() {
|
|
||||||
UserDefaults.standard.set(try? JSONEncoder().encode(palettes), forKey: userDefaultsKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func restoreFromUserDefaults() {
|
|
||||||
if let jsonData = UserDefaults.standard.data(forKey: userDefaultsKey),
|
|
||||||
let decodedPalettes = try? JSONDecoder().decode(Array<Palette>.self, from: jsonData) {
|
|
||||||
palettes = decodedPalettes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(named name: String) {
|
|
||||||
self.name = name
|
|
||||||
restoreFromUserDefaults()
|
|
||||||
if palettes.isEmpty {
|
|
||||||
print("using built-in palettes")
|
|
||||||
insertPalette(named: "Vehicles", emojis: "🚗🚕🚙🚌🚎🏎🚓🚑🚒🚐🛻🚚🚛🚜🦯🦽🦼🛴🚲🛵🏍🛺🚨🚔🚍🚘🚖🛞🚡🚠🚟🚃🚋🚞🚝🚄🚅🚈🚂🚆🚇🚊🚉✈️🛫🛬🛩")
|
|
||||||
insertPalette(named: "Sports", emojis: "⚽️🏀🏈⚾️🥎🎾🏐🏉🥏🎱🪀🏓🏸🏒🏑🥍🏏")
|
|
||||||
} else {
|
|
||||||
print("loaded palettes")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intent
|
|
||||||
|
|
||||||
func palette(at index: Int) -> Palette {
|
|
||||||
let safeIndex = min(max(index, 0), palettes.count - 1)
|
|
||||||
return palettes[safeIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
func removePalette(at index: Int) -> Int {
|
|
||||||
if palettes.count > 1, palettes.indices.contains(index) {
|
|
||||||
palettes.remove(at: index)
|
|
||||||
}
|
|
||||||
return index % palettes.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertPalette(named name: String, emojis: String? = nil, at index: Int = 0) {
|
|
||||||
let unique = (palettes.max(by: { $0.id < $1.id })?.id ?? 0) + 1
|
|
||||||
let palette = Palette(name: name, emojis: emojis ?? "", id: unique)
|
|
||||||
let safeIndex = min(max(index, 0), palettes.count)
|
|
||||||
palettes.insert(palette, at: safeIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user