Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions Elsewhen.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
5EB23BA126E406B900203153 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EB23BA026E406B900203153 /* Assets.xcassets */; };
5EB23BA426E406B900203153 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EB23BA326E406B900203153 /* Preview Assets.xcassets */; };
5EB31FB726E6390D00826907 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EB31FB626E6390D00826907 /* SearchBar.swift */; };
5EE70E2226EC9AA600F3F164 /* TimeZoneFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE70E2126EC9AA500F3F164 /* TimeZoneFlags.swift */; };
5EE70E2426ECBEED00F3F164 /* MykeMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE70E2326ECBEED00F3F164 /* MykeMode.swift */; };
5EE70E2726ECCC0700F3F164 /* TimeCodeGeneratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE70E2626ECCC0700F3F164 /* TimeCodeGeneratorView.swift */; };
635C8FA726E66E8300361B08 /* DiscordFormattedDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 635C8FA626E66E8300361B08 /* DiscordFormattedDate.swift */; };
6378397E26EA5FD60055A10D /* UIApplication+clearLaunchScreenCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6378397D26EA5FD60055A10D /* UIApplication+clearLaunchScreenCache.swift */; };
6378398026EA61AB0055A10D /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6378397F26EA61AB0055A10D /* Launch Screen.storyboard */; };
Expand All @@ -30,6 +33,8 @@
637839B226EBF5FD0055A10D /* ConvertTimeIntentHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637839B126EBF5FD0055A10D /* ConvertTimeIntentHandling.swift */; };
637839B526EBF8130055A10D /* FormatCodeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637839B426EBF8130055A10D /* FormatCodeExtension.swift */; };
637839B626EBF9690055A10D /* DateHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6378398426EB80F50055A10D /* DateHelpers.swift */; };
637839CB26ED11360055A10D /* TimeZone+ItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637839CA26ED11360055A10D /* TimeZone+ItemProvider.swift */; };
63DDFB9A26ED178E0021ACFD /* TimeZone+DropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63DDFB9926ED178E0021ACFD /* TimeZone+DropDelegate.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -72,6 +77,9 @@
5EB23BA026E406B900203153 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
5EB23BA326E406B900203153 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
5EB31FB626E6390D00826907 /* SearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
5EE70E2126EC9AA500F3F164 /* TimeZoneFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZoneFlags.swift; sourceTree = "<group>"; };
5EE70E2326ECBEED00F3F164 /* MykeMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MykeMode.swift; sourceTree = "<group>"; };
5EE70E2626ECCC0700F3F164 /* TimeCodeGeneratorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeCodeGeneratorView.swift; sourceTree = "<group>"; };
635C8FA626E66E8300361B08 /* DiscordFormattedDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordFormattedDate.swift; sourceTree = "<group>"; };
6378397D26EA5FD60055A10D /* UIApplication+clearLaunchScreenCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+clearLaunchScreenCache.swift"; sourceTree = "<group>"; };
6378397F26EA61AB0055A10D /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
Expand All @@ -90,6 +98,8 @@
637839AF26EBF4910055A10D /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = "<group>"; };
637839B126EBF5FD0055A10D /* ConvertTimeIntentHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvertTimeIntentHandling.swift; sourceTree = "<group>"; };
637839B426EBF8130055A10D /* FormatCodeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatCodeExtension.swift; sourceTree = "<group>"; };
637839CA26ED11360055A10D /* TimeZone+ItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeZone+ItemProvider.swift"; sourceTree = "<group>"; };
63DDFB9926ED178E0021ACFD /* TimeZone+DropDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeZone+DropDelegate.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -143,12 +153,12 @@
5EB23B9B26E406B600203153 /* Elsewhen */ = {
isa = PBXGroup;
children = (
5EE70E2526ECCBF400F3F164 /* Views */,
6378398126EB7F510055A10D /* Elements */,
6378397C26EA5FA50055A10D /* Extensions */,
5EE70E2826ED0EE700F3F164 /* Helpers */,
5EB23B9C26E406B600203153 /* ElsewhenApp.swift */,
5EB23B9E26E406B600203153 /* ContentView.swift */,
6378398426EB80F50055A10D /* DateHelpers.swift */,
5EB31FB626E6390D00826907 /* SearchBar.swift */,
5EB23BA026E406B900203153 /* Assets.xcassets */,
5EB23BA226E406B900203153 /* Preview Content */,
6378397F26EA61AB0055A10D /* Launch Screen.storyboard */,
Expand All @@ -164,10 +174,31 @@
path = "Preview Content";
sourceTree = "<group>";
};
5EE70E2526ECCBF400F3F164 /* Views */ = {
isa = PBXGroup;
children = (
5EE70E2626ECCC0700F3F164 /* TimeCodeGeneratorView.swift */,
5EE70E2326ECBEED00F3F164 /* MykeMode.swift */,
);
path = Views;
sourceTree = "<group>";
};
5EE70E2826ED0EE700F3F164 /* Helpers */ = {
isa = PBXGroup;
children = (
6378398426EB80F50055A10D /* DateHelpers.swift */,
5EB31FB626E6390D00826907 /* SearchBar.swift */,
5EE70E2126EC9AA500F3F164 /* TimeZoneFlags.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
6378397C26EA5FA50055A10D /* Extensions */ = {
isa = PBXGroup;
children = (
6378397D26EA5FD60055A10D /* UIApplication+clearLaunchScreenCache.swift */,
637839CA26ED11360055A10D /* TimeZone+ItemProvider.swift */,
63DDFB9926ED178E0021ACFD /* TimeZone+DropDelegate.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -353,11 +384,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5EE70E2226EC9AA600F3F164 /* TimeZoneFlags.swift in Sources */,
5EB23B9F26E406B600203153 /* ContentView.swift in Sources */,
5EE70E2426ECBEED00F3F164 /* MykeMode.swift in Sources */,
5EB31FB726E6390D00826907 /* SearchBar.swift in Sources */,
6378397E26EA5FD60055A10D /* UIApplication+clearLaunchScreenCache.swift in Sources */,
5EAB48DD26E624CD00B04A60 /* TimezoneChoiceView.swift in Sources */,
5EE70E2726ECCC0700F3F164 /* TimeCodeGeneratorView.swift in Sources */,
6378398726EBA7C60055A10D /* DateTimeSelection.swift in Sources */,
63DDFB9A26ED178E0021ACFD /* TimeZone+DropDelegate.swift in Sources */,
637839CB26ED11360055A10D /* TimeZone+ItemProvider.swift in Sources */,
6378398326EB80C30055A10D /* ResultSheet.swift in Sources */,
6378398526EB80F50055A10D /* DateHelpers.swift in Sources */,
635C8FA726E66E8300361B08 /* DiscordFormattedDate.swift in Sources */,
Expand Down
44 changes: 8 additions & 36 deletions Elsewhen/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,19 @@ let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "times")

struct ContentView: View {

@State private var selectedDate = Date()
@State private var selectedFormatStyle: DateFormat = dateFormats[0]
@State private var selectedTimeZone: String = TimeZone.current.identifier
@State private var showLocalTimeInstead: Bool = false

private var discordFormat: String {
var timeIntervalSince1970 = Int(selectedDate.timeIntervalSince1970)

if let tz = TimeZone(identifier: selectedTimeZone) {
timeIntervalSince1970 = Int(convertSelectedDate(from: tz, to: TimeZone.current).timeIntervalSince1970)
} else {
logger.warning("\(selectedTimeZone, privacy: .public) is not a valid timezone identifier!")
}

return "<t:\(timeIntervalSince1970):\(selectedFormatStyle.code.rawValue)>"
}
@State private var selectedTab: Int = 1

var body: some View {
NavigationView {
VStack(spacing: 0) {

ScrollView(showsIndicators: true) {
DateTimeSelection(selectedFormatStyle: $selectedFormatStyle, selectedDate: $selectedDate, selectedTimeZone: $selectedTimeZone)
}

ResultSheet(selectedDate: selectedDate, selectedTimeZone: selectedTimeZone, discordFormat: discordFormat, showLocalTimeInstead: $showLocalTimeInstead, selectedFormatStyle: $selectedFormatStyle)

}
.edgesIgnoringSafeArea([.bottom, .horizontal])
.navigationTitle("Discord Time Code Generator")
.navigationBarTitleDisplayMode(.inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.onChange(of: selectedTimeZone) { _ in
showLocalTimeInstead = false
TabView(selection: $selectedTab) {
TimeCodeGeneratorView()
.tabItem { Label("Time Codes", systemImage: "clock") }
.tag(0)
MykeMode()
.tabItem { Label("Myke Mode", systemImage: "keyboard") }
.tag(1)
}
}

func convertSelectedDate(from initialTimezone: TimeZone, to targetTimezone: TimeZone) -> Date {
return convert(date: selectedDate, from: initialTimezone, to: targetTimezone)
}
}

struct ContentView_Previews: PreviewProvider {
Expand Down
2 changes: 1 addition & 1 deletion Elsewhen/Elements/ResultSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ struct ResultSheet: View {
.frame(minWidth: 0, maxWidth: .infinity)
.background(
Color(UIColor.secondarySystemBackground)
.shadow(radius: 10)
.shadow(radius: 5, x: 0, y: -5)
)
}

Expand Down
39 changes: 39 additions & 0 deletions Elsewhen/Extensions/TimeZone+DropDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// TimeZone+DropDelegate.swift
// TimeZone+DropDelegate
//
// Created by David on 11/09/2021.
//

import Foundation
import SwiftUI
import UniformTypeIdentifiers

extension TimeZone {
struct TZDropDelegate: DropDelegate {
let onDrop: ((TimeZone?) -> ())
func performDrop(info: DropInfo) -> Bool {
let itemProviders = info.itemProviders(for: [UTType.json, UTType.text])
guard !itemProviders.isEmpty else {
return false
}
for itemProvider in itemProviders {
if itemProvider.canLoadObject(ofClass: TimeZone.ItemProvider.self) {
itemProvider.loadObject(ofClass: TimeZone.ItemProvider.self) { tzItemProvider, error in
guard let tzItemProvider = tzItemProvider as? TimeZone.ItemProvider,
error == nil else {
if let error = error {
logger.error("Unabled to load dropped item: \(error.localizedDescription)")
}
onDrop(nil)
return
}
onDrop(TimeZone(identifier: tzItemProvider.identifier))
}
return true
}
}
return false
}
}
}
131 changes: 131 additions & 0 deletions Elsewhen/Extensions/TimeZone+ItemProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// TimeZone+ItemProvider.swift
// TimeZone+ItemProvider
//
// Created by David on 11/09/2021.
//

import Foundation
import UniformTypeIdentifiers

let jsonEncoder = JSONEncoder()
let jsonDecoder = JSONDecoder()

let typeIdentifiers = [
UTType.json.identifier,
UTType.text.identifier, UTType.utf8PlainText.identifier, UTType.plainText.identifier
]

extension TimeZone {
class ItemProvider: NSObject, NSItemProviderWriting, Codable, NSItemProviderReading {

let identifier: String
let name: String?
let abbreviation: String?
let flag: String

var resolvedName: String {
"\(self.flag) \(self.name ?? self.identifier)"
}

required init(from timezone: TimeZone) {
self.identifier = timezone.identifier
self.name = timezone.localizedName(for: .standard, locale: .current)
self.abbreviation = timezone.abbreviation()
self.flag = flagForTimeZone(timezone)
}

static var writableTypeIdentifiersForItemProvider: [String] = typeIdentifiers

func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
switch typeIdentifier {
case UTType.json.identifier:
return self.loadJson(forItemProviderCompletionHandler: completionHandler)
case UTType.text.identifier,
UTType.utf8PlainText.identifier,
UTType.plainText.identifier:
return self.loadUtf8String(forItemProviderCompletionHandler: completionHandler)
default:
completionHandler(nil, ItemProviderError.unrecognisedType(identifier: typeIdentifier))
return nil
}
}

func loadJson(forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
do {
completionHandler(try jsonEncoder.encode(self), nil)
} catch {
completionHandler(nil, error)
}
return nil
}

func loadUtf8String(forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
completionHandler(Data(self.resolvedName.utf8), nil)
return nil
}

static var readableTypeIdentifiersForItemProvider: [String] = typeIdentifiers

static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
switch typeIdentifier {
case UTType.json.identifier:
return try jsonDecoder.decode(Self.self, from: data)
case UTType.text.identifier,
UTType.utf8PlainText.identifier,
UTType.plainText.identifier:
guard let string = String(data: data, encoding: .utf8) else {
throw ItemProviderError.invalidData(representing: "TimeZone")
}
if let provider = Self.from(string: string) {
return provider
}
if let provider = Self.from(string: string.replacingOccurrences(of: "\\", with: "").trimmingCharacters(in: .punctuationCharacters)) {
return provider
}
if let provider = Self.from(string: string.dropFirst(1).trimmingCharacters(in: .whitespaces).trimmingCharacters(in: .punctuationCharacters)) {
return provider
}
throw ItemProviderError.invalidData(representing: "TimeZone")
default:
throw ItemProviderError.unrecognisedType(identifier: typeIdentifier)
}
}

static func from(string: String) -> Self? {
if let tz = TimeZone(identifier: string) {
return Self.init(from: tz)
}
if let tz = TimeZone(abbreviation: string) {
return Self.init(from: tz)
}
return nil
}
}

var itemProvider: ItemProvider {
TimeZone.ItemProvider(from: self)
}
}

enum ItemProviderError: LocalizedError {
case unrecognisedType(identifier: String)
case invalidData(representing: String)

var errorDescription: String? {
return String.localizedStringWithFormat(NSLocalizedString("ItemProvidingFailed: %@", tableName: "Errors", comment: ""), failureReason!)
}

var failureReason: String? {
switch self {
case .unrecognisedType(let identifier):
return String.localizedStringWithFormat(NSLocalizedString("UnrecognisedUTI: %@", tableName: "Errors", comment: ""), identifier)
case .invalidData(let representing):
return "Data did not represent a \(representing)"
}
}

var recoverySuggestion: String? {
"Please contact support"
}
}
File renamed without changes.
File renamed without changes.
Loading