diff --git a/Package.swift b/Package.swift index 3948354..4be51cb 100644 --- a/Package.swift +++ b/Package.swift @@ -7,8 +7,7 @@ let package = Package( name: "FeedbacksKit", defaultLocalization: "en", platforms: [ - .iOS(.v14), - .macOS(.v13), + .iOS(.v14) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. diff --git a/Sources/FeedbacksKit/FeedbackForm/FeedbackForm.swift b/Sources/FeedbacksKit/FeedbackForm/FeedbackForm.swift index c2ef718..952841f 100644 --- a/Sources/FeedbacksKit/FeedbackForm/FeedbackForm.swift +++ b/Sources/FeedbacksKit/FeedbackForm/FeedbackForm.swift @@ -3,24 +3,29 @@ import SwiftUI public struct FeedbackForm: View { public struct Config { - let title: String? - - public init(title: String?) { - self.title = title + let title: String + let textForegroundColor: Color + + public init( + title: String? = nil, + textForegroundColor: Color? = nil + ) { + self.title = title ?? "_send_a_feedback".localized + self.textForegroundColor = textForegroundColor ?? .blue } } @Environment(\.presentationMode) private var presentationMode @StateObject private var viewModel: FeedbackFormViewModel - private let config: Config? + private let config: Config public init( service: SubmitService, config: Config? = nil ) { self._viewModel = StateObject(wrappedValue: FeedbackFormViewModel(service: service)) - self.config = config + self.config = config ?? Config() } public var body: some View { @@ -29,18 +34,20 @@ public struct FeedbackForm: View { formFields submitButton } - .navigationTitle(Text(config?.title ?? "_send_a_feedback".localized)) + .navigationTitle(Text(config.title)) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button { presentationMode.wrappedValue.dismiss() } label: { Text("_cancel".localized) + .foregroundColor(config.textForegroundColor) } } } } .navigationViewStyle(.stack) + .accessibilityIdentifier("LeaveFeedbackScreen") } // MARK: Views @@ -51,12 +58,15 @@ public struct FeedbackForm: View { TextField("_email_placeholder".localized, text: $viewModel.email) .keyboardType(.emailAddress) .autocapitalization(.none) + .autocorrectionDisabled() + .accessibilityIdentifier("EmailTextField") } header: { Text("_email_title".localized) } Section { TextEditor(text: $viewModel.message) + .accessibilityIdentifier("MessageTextEditor") } header: { Text("_message_title".localized) } @@ -75,9 +85,11 @@ public struct FeedbackForm: View { .frame(maxWidth: .infinity) } else { Text("_send_feedback".localized) + .foregroundColor(viewModel.isSubmitDisabled ? .gray : config.textForegroundColor) .frame(maxWidth: .infinity) } } + .accessibilityIdentifier("SendFeedbackButton") .disabled(viewModel.isSubmitDisabled) } footer: { footer @@ -104,6 +116,7 @@ public struct FeedbackForm: View { .bold() .frame(maxWidth: .infinity) .foregroundColor(.green) + .accessibilityIdentifier("SendFeedbackSuccessText") .onAppear { Task { try? await Task.sleep(nanoseconds: 2_000_000_000) @@ -120,6 +133,7 @@ public struct FeedbackForm: View { .bold() .frame(maxWidth: .infinity) .foregroundColor(.red) + .accessibilityIdentifier("SendFeedbackFailureText") .onAppear { Task { try? await Task.sleep(nanoseconds: 3_000_000_000) diff --git a/Sources/FeedbacksKit/FeedbackForm/FeedbackFormViewModel.swift b/Sources/FeedbacksKit/FeedbackForm/FeedbackFormViewModel.swift index 851c029..fdfa502 100644 --- a/Sources/FeedbacksKit/FeedbackForm/FeedbackFormViewModel.swift +++ b/Sources/FeedbacksKit/FeedbackForm/FeedbackFormViewModel.swift @@ -11,7 +11,7 @@ class FeedbackFormViewModel: ObservableObject { var message: String { switch self { case .success: return "_feedback_successfully_sent".localized - case .failure: return "_oups_error_occured".localized + case .failure: return "_oops_error_occurred".localized } } } diff --git a/Sources/FeedbacksKit/Model/FeedbackFormData.swift b/Sources/FeedbacksKit/Model/FeedbackFormData.swift index 2c333c3..ba27c2c 100644 --- a/Sources/FeedbacksKit/Model/FeedbackFormData.swift +++ b/Sources/FeedbacksKit/Model/FeedbackFormData.swift @@ -1,12 +1,24 @@ +import UIKit + public struct FeedbackFormData { let email: String let message: String + let deviceModelIdentifier: String + let systemNameAndVersion: String + let appVersion: String + let language: String + public init( email: String, message: String ) { self.email = email self.message = message + + deviceModelIdentifier = UIDevice.modelIdentifier + systemNameAndVersion = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)" + appVersion = "\(Bundle.main.appVersionLong) (\(Bundle.main.appBuild))" + language = Locale.current.localizedString(forIdentifier: Locale.current.identifier) ?? Locale.current.identifier } } diff --git a/Sources/FeedbacksKit/Resources/en.lproj/Localizable.strings b/Sources/FeedbacksKit/Resources/en.lproj/Localizable.strings index 67e2b13..d66ce51 100644 --- a/Sources/FeedbacksKit/Resources/en.lproj/Localizable.strings +++ b/Sources/FeedbacksKit/Resources/en.lproj/Localizable.strings @@ -8,4 +8,4 @@ "_send_feedback" = "Send feedback"; "_feedback_successfully_sent" = "Feedback successfully sent!"; -"_oups_error_occured" = "Ooups! An error occurred."; +"_oops_error_occurred" = "Oops! An error occurred."; diff --git a/Sources/FeedbacksKit/Resources/fr.lproj/Localizable.strings b/Sources/FeedbacksKit/Resources/fr.lproj/Localizable.strings index 7271fb8..a9fde94 100644 --- a/Sources/FeedbacksKit/Resources/fr.lproj/Localizable.strings +++ b/Sources/FeedbacksKit/Resources/fr.lproj/Localizable.strings @@ -8,4 +8,4 @@ "_send_feedback" = "Envoyer"; "_feedback_successfully_sent" = "Message envoyé !"; -"_oups_error_occured" = "Ooups! Il y a eu une erreur..."; +"_oops_error_occurred" = "Ooups! Il y a eu une erreur..."; diff --git a/Sources/FeedbacksKit/Service/NotionSubmitService/NotionSubmitService.swift b/Sources/FeedbacksKit/Service/NotionSubmitService/NotionSubmitService.swift index be19234..879b476 100644 --- a/Sources/FeedbacksKit/Service/NotionSubmitService/NotionSubmitService.swift +++ b/Sources/FeedbacksKit/Service/NotionSubmitService/NotionSubmitService.swift @@ -73,6 +73,75 @@ public struct NotionSubmitService: SubmitService { ) ] ) + ), + .init(type: "divider", divider: .init()), + .init( + object: "block", + type: "paragraph", + paragraph: .init( + richText: [ + .init( + type: "text", + text: .init(content: "App version: "), + annotations: .init(bold: true) + ), + .init( + type: "text", + text: .init(content: formData.appVersion) + ) + ] + ) + ), + .init( + object: "block", + type: "paragraph", + paragraph: .init( + richText: [ + .init( + type: "text", + text: .init(content: "Device model identifier: "), + annotations: .init(bold: true) + ), + .init( + type: "text", + text: .init(content: formData.deviceModelIdentifier) + ) + ] + ) + ), + .init( + object: "block", + type: "paragraph", + paragraph: .init( + richText: [ + .init( + type: "text", + text: .init(content: "System: "), + annotations: .init(bold: true) + ), + .init( + type: "text", + text: .init(content: formData.systemNameAndVersion) + ) + ] + ) + ), + .init( + object: "block", + type: "paragraph", + paragraph: .init( + richText: [ + .init( + type: "text", + text: .init(content: "System Locale: "), + annotations: .init(bold: true) + ), + .init( + type: "text", + text: .init(content: formData.language) + ) + ] + ) ) ] ) @@ -113,9 +182,12 @@ private struct NotionBody: Codable { } struct Child: Codable { - let object: String + var object: String? let type: String - let paragraph: Paragraph + var paragraph: Paragraph? + var divider: Divider? + + struct Divider: Codable {} struct Paragraph: Codable { let richText: [RichText] @@ -123,6 +195,15 @@ private struct NotionBody: Codable { struct RichText: Codable { let type: String let text: Text + struct Annotations: Codable { + var bold = false + var italic = false + var strikethrough = false + var underline = false + var code = false + var color = "default" + } + var annotations: Annotations? struct Text: Codable { let content: String diff --git a/Sources/FeedbacksKit/Utils/Bundle+VersionInfo.swift b/Sources/FeedbacksKit/Utils/Bundle+VersionInfo.swift new file mode 100644 index 0000000..d01e2d2 --- /dev/null +++ b/Sources/FeedbacksKit/Utils/Bundle+VersionInfo.swift @@ -0,0 +1,11 @@ +import Foundation + +extension Bundle { + var appBuild: String { getInfo("CFBundleVersion") } + var appVersionLong: String { getInfo("CFBundleShortVersionString") } + var appVersionShort: String { getInfo("CFBundleShortVersion") } + + private func getInfo(_ string: String) -> String { + infoDictionary?[string] as? String ?? "⚠️" + } +} diff --git a/Sources/FeedbacksKit/Utils/UIDevice+ModelInfo.swift b/Sources/FeedbacksKit/Utils/UIDevice+ModelInfo.swift new file mode 100644 index 0000000..2f0a8e4 --- /dev/null +++ b/Sources/FeedbacksKit/Utils/UIDevice+ModelInfo.swift @@ -0,0 +1,22 @@ +import UIKit + +extension UIDevice { + + /// The device model identifier e.g. "iPhone14,5" (iPhone 13) or "Simulator" + static var modelIdentifier: String { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + + switch identifier { + case "i386", "x86_64": + return "Simulator" + default: + return identifier + } + } +} diff --git a/Sources/FeedbacksKit/Utils/View+hideKeyboard.swift b/Sources/FeedbacksKit/Utils/View+hideKeyboard.swift index a24c81c..c40bef6 100644 --- a/Sources/FeedbacksKit/Utils/View+hideKeyboard.swift +++ b/Sources/FeedbacksKit/Utils/View+hideKeyboard.swift @@ -7,3 +7,7 @@ extension View { } } #endif + + + +