diff --git a/StickerSlack.xcodeproj/project.pbxproj b/StickerSlack.xcodeproj/project.pbxproj index bbaa64d..2ddd742 100644 --- a/StickerSlack.xcodeproj/project.pbxproj +++ b/StickerSlack.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ A9104C7C2EB3AE6300D160EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A949B1EF2EA04E8200215164 /* Assets.xcassets */; }; A9104C7F2EB4022500D160EA /* MSSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9104C7D2EB4022500D160EA /* MSSticker.swift */; }; A9104C802EB4022500D160EA /* MSSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9104C7D2EB4022500D160EA /* MSSticker.swift */; }; + A921C2DF2ED067BB00E57B1A /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A921C2DE2ED067BB00E57B1A /* WelcomeView.swift */; }; + A921C2E02ED067BB00E57B1A /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A921C2DE2ED067BB00E57B1A /* WelcomeView.swift */; }; A924C3732EA9127200F20781 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = A924C3712EA9127200F20781 /* Emoji.swift */; }; A924C3782EA9225800F20781 /* Haptics in Frameworks */ = {isa = PBXBuildFile; productRef = A924C3772EA9225800F20781 /* Haptics */; }; A931D4082EBC9646007BC75B /* Haptics in Frameworks */ = {isa = PBXBuildFile; productRef = A931D4072EBC9646007BC75B /* Haptics */; }; @@ -107,6 +109,7 @@ A9104C722EB3AE4700D160EA /* Icon.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = Icon.pxd; sourceTree = ""; }; A9104C732EB3AE4700D160EA /* StickerSlack.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = StickerSlack.icon; sourceTree = ""; }; A9104C7D2EB4022500D160EA /* MSSticker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSSticker.swift; sourceTree = ""; }; + A921C2DE2ED067BB00E57B1A /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; A924C3712EA9127200F20781 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = ""; }; A924C3742EA9134C00F20781 /* StickerSlack.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StickerSlack.entitlements; sourceTree = ""; }; A935437A2EB2A3C800BB80A4 /* FilterCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterCategory.swift; sourceTree = ""; }; @@ -237,6 +240,7 @@ A955B3F02EC22E9700E1732D /* BrowseView.swift */, A955B3F42EC22EE900E1732D /* SearchView.swift */, A957C1732ECCE2CE00EA3EE9 /* SettingsView.swift */, + A921C2DE2ED067BB00E57B1A /* WelcomeView.swift */, ); path = SwiftUI; sourceTree = ""; @@ -495,6 +499,7 @@ A9EB724F2EB94A6B00658CEB /* TrieTestingView.swift in Sources */, A924C3732EA9127200F20781 /* Emoji.swift in Sources */, A9D15B8B2EB1142C00404792 /* EmojiPack.swift in Sources */, + A921C2DF2ED067BB00E57B1A /* WelcomeView.swift in Sources */, A949B1F82EA04F2300215164 /* EmojiHoarder.swift in Sources */, A9BBC5182EB8FA4500FFE82F /* ViewModifiers.swift in Sources */, A9EB72492EB948C400658CEB /* EmojiRow.swift in Sources */, @@ -538,6 +543,7 @@ A955B3F62EC22EE900E1732D /* SearchView.swift in Sources */, A9B9A8322EB2CD29004C9245 /* SlackResponse.swift in Sources */, A957C1782ECD008E00EA3EE9 /* Bundle.swift in Sources */, + A921C2E02ED067BB00E57B1A /* WelcomeView.swift in Sources */, A9B9A82E2EB2CCBE004C9245 /* StickerSlackTests.swift in Sources */, A9B9A8312EB2CD14004C9245 /* FilterCategory.swift in Sources */, A957C1802ECFAA1100EA3EE9 /* GifView.swift in Sources */, @@ -727,6 +733,7 @@ STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; @@ -774,6 +781,7 @@ STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/StickerSlack/Emoji/EmojiHoarder.swift b/StickerSlack/Emoji/EmojiHoarder.swift index 1e752bc..45640b3 100644 --- a/StickerSlack/Emoji/EmojiHoarder.swift +++ b/StickerSlack/Emoji/EmojiHoarder.swift @@ -26,8 +26,13 @@ class EmojiHoarder: ObservableObject { @Published var downloadedEmojis: Set = [] @Published var downloadedEmojisArr: [String] = [] @Published var searchTerm: String = "" + @Published var letterStats: [EmojiHoarder.LetterStat] = [] + + @Published var showWelcome: Bool = true init(localOnly: Bool = false, skipIndex: Bool = false) { + self.showWelcome = !UserDefaults.standard.bool(forKey: "showWelcome") + let localDB = loadLocalDB() withAnimation { self.emojis = localDB } loadTrie() @@ -196,6 +201,26 @@ class EmojiHoarder: ObservableObject { Haptic.heavy.trigger() } + func setShowWelcome(to newValue: Bool) { + UserDefaults.standard.set(!newValue, forKey: "shownWelcome") + self.showWelcome = newValue + } + + func generateLetterStats() -> [EmojiHoarder.LetterStat] { + var result: [EmojiHoarder.LetterStat] = [] + for child in trie.root.children { + let count = trie.collectWords(startingWith: child.key, from: child.value).count + let stat = LetterStat(char: child.key, count: count) + result.append(stat) + } + return result + } + + struct LetterStat: Hashable { + var char: String + var count: Int + } + // func filterEmojis(byCategory category: FilterCategory, searchTerm: String) { // guard category != .none else { // filterEmojis(by: searchTerm) diff --git a/StickerSlack/SwiftUI/ContentView.swift b/StickerSlack/SwiftUI/ContentView.swift index 6ecea42..4407e28 100644 --- a/StickerSlack/SwiftUI/ContentView.swift +++ b/StickerSlack/SwiftUI/ContentView.swift @@ -12,40 +12,48 @@ struct ContentView: View { @ObservedObject var hoarder: EmojiHoarder = EmojiHoarder() var body: some View { - if #available(iOS 18, *) { - TabView { - Tab("Browse", systemImage: "square.grid.2x2.fill") { - BrowseView(hoarder: hoarder) + Group { + if #available(iOS 18, *) { + TabView { + Tab("Browse", systemImage: "square.grid.2x2.fill") { + BrowseView(hoarder: hoarder) + } + + Tab("Downloaded", systemImage: "arrow.down.circle.fill") { + DownloadedView(hoarder: hoarder) + } + + Tab("Settings", systemImage: "gear") { + SettingsView(hoarder: hoarder) + } + + Tab(role: .search) { + SearchView(hoarder: hoarder) + } } - - Tab("Downloaded", systemImage: "arrow.down.circle.fill") { + } else { + TabView { DownloadedView(hoarder: hoarder) - } - - Tab("Settings", systemImage: "gear") { - SettingsView(hoarder: hoarder) - } - - Tab(role: .search) { + .tabItem { + Label("Downloaded", systemImage: "arrow.down.circle.fill") + } + BrowseView(hoarder: hoarder) + .tabItem { + Label("Browse", systemImage: "square.grid.2x2.fill") + } SearchView(hoarder: hoarder) + .tabItem { + Label("Search", systemImage: "magnifyingglass") + } } } - } else { - TabView { - DownloadedView(hoarder: hoarder) - .tabItem { - Label("Downloaded", systemImage: "arrow.down.circle.fill") - } - BrowseView(hoarder: hoarder) - .tabItem { - Label("Browse", systemImage: "square.grid.2x2.fill") - } - SearchView(hoarder: hoarder) - .tabItem { - Label("Search", systemImage: "magnifyingglass") - } - } } + .sheet(isPresented: $hoarder.showWelcome) { + print("hi") + } content: { + WelcomeView() + } + } } diff --git a/StickerSlack/SwiftUI/DownloadedView.swift b/StickerSlack/SwiftUI/DownloadedView.swift index 52678ce..6f161bb 100644 --- a/StickerSlack/SwiftUI/DownloadedView.swift +++ b/StickerSlack/SwiftUI/DownloadedView.swift @@ -24,40 +24,42 @@ struct DownloadedView: View { } var body: some View { - ScrollView { - let columns: Int = max(1, Int((UIScreen.main.bounds.width - 2*spacing) / (minColWidth + spacing))) - let layout = Array(repeating: col, count: columns) - LazyVGrid(columns: layout, spacing: spacing) { - ForEach(hoarder.downloadedEmojisArr, id: \.self) { name in - if let emoji = hoarder.trie.dict[name] { - ZStack { - Rectangle() - .foregroundStyle(isDark ? .black : .white) - EmojiPreview(hoarder: hoarder, emoji: emoji) - RoundedRectangle(cornerRadius: 15) - .stroke(.gray, lineWidth: 1) - } - .aspectRatio(1, contentMode: .fit) - .clipShape(RoundedRectangle(cornerRadius: 15)) - .contextMenu { - Text(emoji.name) - Button("Copy Name", systemImage: "doc.on.clipboard") { - UIPasteboard.general.string = emoji.name + GeometryReader { geo in + ScrollView { + let columns: Int = max(1, Int((geo.size.width - 2*spacing) / (minColWidth + spacing))) + let layout = Array(repeating: col, count: columns) + LazyVGrid(columns: layout, spacing: spacing) { + ForEach(hoarder.downloadedEmojisArr, id: \.self) { name in + if let emoji = hoarder.trie.dict[name] { + ZStack { + Rectangle() + .foregroundStyle(isDark ? .black : .white) + EmojiPreview(hoarder: hoarder, emoji: emoji) + RoundedRectangle(cornerRadius: 15) + .stroke(.gray, lineWidth: 1) } - Button("Copy Image", systemImage: "photo.fill.on.rectangle.fill") { - UIPasteboard.general.image = emoji.image - } - Divider() - ShareLink("Share", item: emoji.remoteImageURL, subject: nil, message: nil) - Divider() - Button("Delete", systemImage: "trash.fill", role: .destructive) { - hoarder.delete(emoji: emoji) + .aspectRatio(1, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 15)) + .contextMenu { + Text(emoji.name) + Button("Copy Name", systemImage: "doc.on.clipboard") { + UIPasteboard.general.string = emoji.name + } + Button("Copy Image", systemImage: "photo.fill.on.rectangle.fill") { + UIPasteboard.general.image = emoji.image + } + Divider() + ShareLink("Share", item: emoji.remoteImageURL, subject: nil, message: nil) + Divider() + Button("Delete", systemImage: "trash.fill", role: .destructive) { + hoarder.delete(emoji: emoji) + } } } } } + .padding(.horizontal, spacing) } - .padding(.horizontal, spacing) } } } diff --git a/StickerSlack/SwiftUI/SettingsView.swift b/StickerSlack/SwiftUI/SettingsView.swift index e89171e..a6c7172 100644 --- a/StickerSlack/SwiftUI/SettingsView.swift +++ b/StickerSlack/SwiftUI/SettingsView.swift @@ -25,16 +25,36 @@ struct SettingsView: View { .font(.title) .monospaced() .bold() + HStack(alignment: .center, spacing: 5) { + Text(Bundle.main.appVersion) + .bold() + Text(Bundle.main.appBuild) + .foregroundStyle(.gray) + } } } Section { - Text("") + Text("\(hoarder.emojis.count) total Emoji") + Text("\(hoarder.downloadedEmojis.count) downloaded Emoji") + NavigationLink { + List { + ForEach(hoarder.generateLetterStats(), id: \.self) { stat in + Text("\(stat.count) Emoji starting with \(stat.char)") + } + } + } label: { + Label("Letter Stats", systemImage: "textformat") + } + } + + Button("Show Welcome", systemImage: "arrow.trianglehead.clockwise") { + hoarder.setShowWelcome(to: true) } Section("Debug") { NavigationLink { - TrieTestingView() + TrieTestingView(hoarder: hoarder) } label: { Label("Tree", systemImage: "tree") } diff --git a/StickerSlack/SwiftUI/WelcomeView.swift b/StickerSlack/SwiftUI/WelcomeView.swift new file mode 100644 index 0000000..1dd5442 --- /dev/null +++ b/StickerSlack/SwiftUI/WelcomeView.swift @@ -0,0 +1,49 @@ +// +// WelcomeView.swift +// StickerSlack +// +// Created by neon443 on 21/11/2025. +// + +import SwiftUI + +struct WelcomeView: View { + var body: some View { + VStack { + Text("StickerSlack") + .bold() + .font(.title) + .monospaced() + .padding() + List { + Section("How to use") { + ListRow(number: 1, text: "Browse or search for an emoji") + ListRow(number: 2, text: "Download it") + ListRow(number: 3, text: "Open iMessage") + ListRow(number: 4, text: "Press the +") + ListRow(number: 5, text: "Choose StickerSlack") + ListRow(number: 6, text: "Tap an emoji to use it!") + } + } + .scrollContentBackground(.hidden) + } + } +} + +struct ListRow: View { + @State var number: Int + @State var text: String + + var body: some View { + HStack { + Text("\(number)") + .padding(.trailing, 10) + .foregroundStyle(.gray) + Text(text) + } + } +} + +#Preview { + WelcomeView() +} diff --git a/StickerSlack/Trie/TrieTestingView.swift b/StickerSlack/Trie/TrieTestingView.swift index cd4651a..9b8439f 100644 --- a/StickerSlack/Trie/TrieTestingView.swift +++ b/StickerSlack/Trie/TrieTestingView.swift @@ -8,7 +8,7 @@ import SwiftUI struct TrieTestingView: View { - @ObservedObject var hoarder: EmojiHoarder = EmojiHoarder(localOnly: true) + @ObservedObject var hoarder: EmojiHoarder @State var searchTerm: String = "" @State var searchStatus: Bool? = nil @@ -99,5 +99,5 @@ struct TrieNodeView: View { } #Preview { - TrieTestingView() + TrieTestingView(hoarder: EmojiHoarder(localOnly: true)) }