diff --git a/NativeAppTemplate.xcodeproj/project.pbxproj b/NativeAppTemplate.xcodeproj/project.pbxproj index 5c8a420..78b6b43 100644 --- a/NativeAppTemplate.xcodeproj/project.pbxproj +++ b/NativeAppTemplate.xcodeproj/project.pbxproj @@ -1268,103 +1268,6 @@ }; name = Release; }; - 016595AF2824E3D800203F7F /* Beta configuration for PBXProject "NativeAppTemplate" */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = NNYDL5U3V3; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 6.0; - VALIDATE_PRODUCT = YES; - }; - name = Beta; - }; - 016595B02824E3D800203F7F /* Beta configuration for PBXNativeTarget "NativeAppTemplate" */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; - BUNDLE_ID_SUFFIX = .beta; - CODE_SIGN_ENTITLEMENTS = ""; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; - DEVELOPMENT_ASSET_PATHS = "\"NativeAppTemplate/Preview Content\""; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - INFOPLIST_FILE = NativeAppTemplate/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "NativeAppTemplate Free"; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; - IPHONEOS_DEPLOYMENT_TARGET = 26.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 3.1.1; - PRODUCT_BUNDLE_IDENTIFIER = "com.nativeapptemplate.NativeAppTemplateFree.ios${SAMPLE_CODE_DISAMBIGUATOR}$(BUNDLE_ID_SUFFIX)"; - PRODUCT_NAME = NativeAppTemplate; - SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_VERSION = 6.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Beta; - }; 01D19B4A2D4DE33500BDEAB7 /* Debug configuration for PBXNativeTarget "NativeAppTemplateTests" */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1406,26 +1309,6 @@ }; name = Release; }; - 01D19B4C2D4DE33500BDEAB7 /* Beta configuration for PBXNativeTarget "NativeAppTemplateTests" */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GCC_C_LANGUAGE_STANDARD = gnu17; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.2; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.nativeapptemplate.NativeAppTemplateTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NativeAppTemplate.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NativeAppTemplate"; - }; - name = Beta; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1434,7 +1317,6 @@ buildConfigurations = ( 011F6E10259EF16600BED22E /* Debug configuration for PBXProject "NativeAppTemplate" */, 011F6E11259EF16600BED22E /* Release configuration for PBXProject "NativeAppTemplate" */, - 016595AF2824E3D800203F7F /* Beta configuration for PBXProject "NativeAppTemplate" */, ); defaultConfigurationName = Release; }; @@ -1443,7 +1325,6 @@ buildConfigurations = ( 011F6E13259EF16600BED22E /* Debug configuration for PBXNativeTarget "NativeAppTemplate" */, 011F6E14259EF16600BED22E /* Release configuration for PBXNativeTarget "NativeAppTemplate" */, - 016595B02824E3D800203F7F /* Beta configuration for PBXNativeTarget "NativeAppTemplate" */, ); defaultConfigurationName = Release; }; @@ -1452,7 +1333,6 @@ buildConfigurations = ( 01D19B4A2D4DE33500BDEAB7 /* Debug configuration for PBXNativeTarget "NativeAppTemplateTests" */, 01D19B4B2D4DE33500BDEAB7 /* Release configuration for PBXNativeTarget "NativeAppTemplateTests" */, - 01D19B4C2D4DE33500BDEAB7 /* Beta configuration for PBXNativeTarget "NativeAppTemplateTests" */, ); defaultConfigurationName = Release; }; diff --git a/NativeAppTemplate/Extensions/Date+Extensions.swift b/NativeAppTemplate/Extensions/Date+Extensions.swift index 5d7f7ea..a4f6bcc 100644 --- a/NativeAppTemplate/Extensions/Date+Extensions.swift +++ b/NativeAppTemplate/Extensions/Date+Extensions.swift @@ -24,9 +24,4 @@ extension Date { var cardDateTimeString: String { "\(cardDateString) \(cardTimeString)" } - - var cardTimeAgoInWordsDateString: String { - let formatter = DateFormatter.timeAgoInWordsDateFormatter - return formatter.string(from: self) - } } diff --git a/NativeAppTemplate/Extensions/DateFormatter+Extensions.swift b/NativeAppTemplate/Extensions/DateFormatter+Extensions.swift index ae0b4ec..2913614 100644 --- a/NativeAppTemplate/Extensions/DateFormatter+Extensions.swift +++ b/NativeAppTemplate/Extensions/DateFormatter+Extensions.swift @@ -33,14 +33,6 @@ extension DateFormatter { static let cardTimeFormatter: DateFormatter = .formatter(for: "HH:mm") - static let timeAgoInWordsDateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .short - dateFormatter.timeStyle = .medium - dateFormatter.doesRelativeDateFormatting = true - return dateFormatter - }() - static func formatter(for dateString: String) -> DateFormatter { let dateFormatter = DateFormatter() dateFormatter.dateFormat = dateString diff --git a/NativeAppTemplate/Extensions/String+Extensions.swift b/NativeAppTemplate/Extensions/String+Extensions.swift index 1273346..644a1ef 100644 --- a/NativeAppTemplate/Extensions/String+Extensions.swift +++ b/NativeAppTemplate/Extensions/String+Extensions.swift @@ -3,36 +3,15 @@ // NativeAppTemplate // -import UIKit +import Foundation extension String { - /// Generates a `UIImage` instance from this string using a specified - /// attributes and size. - /// - /// - Parameters: - /// - attributes: to draw this string with. Default is `nil`. - /// - size: of the image to return. - /// - Returns: a `UIImage` instance from this string using a specified - /// attributes and size, or `nil` if the operation fails. - func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? { - let size = size ?? (self as NSString).size(withAttributes: attributes) - return UIGraphicsImageRenderer(size: size).image { _ in - (self as NSString).draw( - in: CGRect(origin: .zero, size: size), - withAttributes: attributes - ) - } + var isBlank: Bool { + trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } - func isAlphanumeric() -> Bool { - rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil && !isEmpty - } - - func isAlphanumeric(ignoreDiacritics: Bool = false) -> Bool { - if ignoreDiacritics { - range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil && !isEmpty - } else { - isAlphanumeric() - } + var isValidEmail: Bool { + let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + return NSPredicate(format: "SELF MATCHES %@", emailRegex).evaluate(with: self) } } diff --git a/NativeAppTemplate/UI/App Root/ForgotPasswordViewModel.swift b/NativeAppTemplate/UI/App Root/ForgotPasswordViewModel.swift index db169c0..eb932d9 100644 --- a/NativeAppTemplate/UI/App Root/ForgotPasswordViewModel.swift +++ b/NativeAppTemplate/UI/App Root/ForgotPasswordViewModel.swift @@ -25,11 +25,11 @@ final class ForgotPasswordViewModel { } var hasInvalidData: Bool { - if Utility.isBlank(email) { + if email.isBlank { return true } - if !Utility.validateEmail(email) { + if !email.isValidEmail { return true } @@ -37,11 +37,11 @@ final class ForgotPasswordViewModel { } var isEmailBlank: Bool { - Utility.isBlank(email) + email.isBlank } var isEmailInvalid: Bool { - !Utility.isBlank(email) && !Utility.validateEmail(email) + !email.isBlank && !email.isValidEmail } func sendMeResetPasswordInstructionsTapped() { diff --git a/NativeAppTemplate/UI/App Root/ResendConfirmationInstructionsViewModel.swift b/NativeAppTemplate/UI/App Root/ResendConfirmationInstructionsViewModel.swift index 949eb72..03f0fe2 100644 --- a/NativeAppTemplate/UI/App Root/ResendConfirmationInstructionsViewModel.swift +++ b/NativeAppTemplate/UI/App Root/ResendConfirmationInstructionsViewModel.swift @@ -25,11 +25,11 @@ final class ResendConfirmationInstructionsViewModel { } var hasInvalidData: Bool { - if Utility.isBlank(email) { + if email.isBlank { return true } - if !Utility.validateEmail(email) { + if !email.isValidEmail { return true } @@ -37,11 +37,11 @@ final class ResendConfirmationInstructionsViewModel { } var isEmailBlank: Bool { - Utility.isBlank(email) + email.isBlank } var isEmailInvalid: Bool { - !Utility.isBlank(email) && !Utility.validateEmail(email) + !email.isBlank && !email.isValidEmail } func sendMeConfirmationInstructionsTapped() { diff --git a/NativeAppTemplate/UI/App Root/SignInEmailAndPasswordViewModel.swift b/NativeAppTemplate/UI/App Root/SignInEmailAndPasswordViewModel.swift index 3bb1a40..0cab50a 100644 --- a/NativeAppTemplate/UI/App Root/SignInEmailAndPasswordViewModel.swift +++ b/NativeAppTemplate/UI/App Root/SignInEmailAndPasswordViewModel.swift @@ -25,11 +25,11 @@ final class SignInEmailAndPasswordViewModel { } var hasInvalidData: Bool { - if Utility.isBlank(email) || Utility.isBlank(password) { + if email.isBlank || password.isBlank { return true } - if !Utility.validateEmail(email) { + if !email.isValidEmail { return true } @@ -41,7 +41,7 @@ final class SignInEmailAndPasswordViewModel { } var hasInvalidDataPassword: Bool { - if Utility.isBlank(password) { + if password.isBlank { return true } @@ -49,15 +49,15 @@ final class SignInEmailAndPasswordViewModel { } var isEmailBlank: Bool { - Utility.isBlank(email) + email.isBlank } var isEmailInvalid: Bool { - Utility.isBlank(email) || !Utility.validateEmail(email) + email.isBlank || !email.isValidEmail } var isPasswordBlank: Bool { - Utility.isBlank(password) + password.isBlank } func signIn() { diff --git a/NativeAppTemplate/UI/App Root/SignUpViewModel.swift b/NativeAppTemplate/UI/App Root/SignUpViewModel.swift index 4d8f465..aae2116 100644 --- a/NativeAppTemplate/UI/App Root/SignUpViewModel.swift +++ b/NativeAppTemplate/UI/App Root/SignUpViewModel.swift @@ -31,7 +31,7 @@ final class SignUpViewModel { } var hasInvalidData: Bool { - if Utility.isBlank(name) { + if name.isBlank { return true } @@ -47,11 +47,11 @@ final class SignUpViewModel { } var hasInvalidDataEmail: Bool { - if Utility.isBlank(email) { + if email.isBlank { return true } - if !Utility.validateEmail(email) { + if !email.isValidEmail { return true } @@ -59,7 +59,7 @@ final class SignUpViewModel { } var hasInvalidDataPassword: Bool { - if Utility.isBlank(password) { + if password.isBlank { return true } @@ -71,15 +71,15 @@ final class SignUpViewModel { } var isNameBlank: Bool { - Utility.isBlank(name) + name.isBlank } var isEmailBlank: Bool { - Utility.isBlank(email) + email.isBlank } var isPasswordBlank: Bool { - Utility.isBlank(password) + password.isBlank } func createShopkeeper() { diff --git a/NativeAppTemplate/UI/Settings/PasswordEditView.swift b/NativeAppTemplate/UI/Settings/PasswordEditView.swift index 9df3a26..49ed3b6 100644 --- a/NativeAppTemplate/UI/Settings/PasswordEditView.swift +++ b/NativeAppTemplate/UI/Settings/PasswordEditView.swift @@ -52,7 +52,7 @@ private extension PasswordEditView { Text(Strings.weNeedYourCurrentPassword) .font(.uiFootnote) Text(Strings.currentPasswordIsRequired) - .foregroundStyle(Utility.isBlank(viewModel.currentPassword) ? .validationError : .clear) + .foregroundStyle(viewModel.currentPassword.isBlank ? .validationError : .clear) .font(.uiFootnote) } } @@ -68,7 +68,7 @@ private extension PasswordEditView { Text("\(viewModel.minimumPasswordLength) characters minimum.") .font(.uiFootnote) - if Utility.isBlank(viewModel.password) { + if viewModel.password.isBlank { Text(Strings.newPasswordIsRequired) .foregroundStyle(.validationError) .font(.uiFootnote) @@ -89,7 +89,7 @@ private extension PasswordEditView { } footer: { Text(Strings.confirmNewPasswordIsRequired) .font(.uiFootnote) - .foregroundStyle(Utility.isBlank(viewModel.passwordConfirmation) ? .validationError : .clear) + .foregroundStyle(viewModel.passwordConfirmation.isBlank ? .validationError : .clear) } } .navigationTitle(Strings.updatePassword) diff --git a/NativeAppTemplate/UI/Settings/PasswordEditViewModel.swift b/NativeAppTemplate/UI/Settings/PasswordEditViewModel.swift index 23e42e3..1299bbc 100644 --- a/NativeAppTemplate/UI/Settings/PasswordEditViewModel.swift +++ b/NativeAppTemplate/UI/Settings/PasswordEditViewModel.swift @@ -31,9 +31,9 @@ final class PasswordEditViewModel { } var hasInvalidData: Bool { - if Utility.isBlank(currentPassword) || - Utility.isBlank(password) || - Utility.isBlank(passwordConfirmation) { + if currentPassword.isBlank || + password.isBlank || + passwordConfirmation.isBlank { return true } @@ -45,7 +45,7 @@ final class PasswordEditViewModel { } var hasInvalidDataPassword: Bool { - if Utility.isBlank(password) { + if password.isBlank { return true } diff --git a/NativeAppTemplate/UI/Settings/ShopkeeperEditView.swift b/NativeAppTemplate/UI/Settings/ShopkeeperEditView.swift index f1d866a..b339a4c 100644 --- a/NativeAppTemplate/UI/Settings/ShopkeeperEditView.swift +++ b/NativeAppTemplate/UI/Settings/ShopkeeperEditView.swift @@ -47,7 +47,7 @@ private extension ShopkeeperEditView { Text(Strings.fullName) } footer: { Text(Strings.fullNameIsRequired) - .foregroundStyle(Utility.isBlank(viewModel.name) ? .validationError : .clear) + .foregroundStyle(viewModel.name.isBlank ? .validationError : .clear) } Section { @@ -57,7 +57,7 @@ private extension ShopkeeperEditView { } header: { Text(Strings.email) } footer: { - if Utility.isBlank(viewModel.email) { + if viewModel.email.isBlank { Text(Strings.emailIsRequired) .foregroundStyle(.validationError) } else if viewModel.hasInvalidDataEmail { diff --git a/NativeAppTemplate/UI/Settings/ShopkeeperEditViewModel.swift b/NativeAppTemplate/UI/Settings/ShopkeeperEditViewModel.swift index 8f728a9..fd0f361 100644 --- a/NativeAppTemplate/UI/Settings/ShopkeeperEditViewModel.swift +++ b/NativeAppTemplate/UI/Settings/ShopkeeperEditViewModel.swift @@ -45,7 +45,7 @@ final class ShopkeeperEditViewModel { } var hasInvalidData: Bool { - if Utility.isBlank(name) { + if name.isBlank { return true } @@ -63,11 +63,11 @@ final class ShopkeeperEditViewModel { } var hasInvalidDataEmail: Bool { - if Utility.isBlank(email) { + if email.isBlank { return true } - if !Utility.validateEmail(email) { + if !email.isValidEmail { return true } diff --git a/NativeAppTemplate/UI/Shop List/ShopCreateViewModel.swift b/NativeAppTemplate/UI/Shop List/ShopCreateViewModel.swift index 3c09430..77eed06 100644 --- a/NativeAppTemplate/UI/Shop List/ShopCreateViewModel.swift +++ b/NativeAppTemplate/UI/Shop List/ShopCreateViewModel.swift @@ -35,7 +35,7 @@ final class ShopCreateViewModel { } var hasInvalidDataName: Bool { - if Utility.isBlank(name) { + if name.isBlank { return true } if name.count > maximumNameLength { diff --git a/NativeAppTemplate/UI/Shop Settings/ItemTag Detail/ItemTagEditViewModel.swift b/NativeAppTemplate/UI/Shop Settings/ItemTag Detail/ItemTagEditViewModel.swift index ba35e86..7216a61 100644 --- a/NativeAppTemplate/UI/Shop Settings/ItemTag Detail/ItemTagEditViewModel.swift +++ b/NativeAppTemplate/UI/Shop Settings/ItemTag Detail/ItemTagEditViewModel.swift @@ -53,7 +53,7 @@ final class ItemTagEditViewModel { } var hasInvalidDataName: Bool { - if Utility.isBlank(name) { + if name.isBlank { return true } if name.count > maximumNameLength { diff --git a/NativeAppTemplate/UI/Shop Settings/ItemTag List/ItemTagCreateViewModel.swift b/NativeAppTemplate/UI/Shop Settings/ItemTag List/ItemTagCreateViewModel.swift index ff3bd15..c00c399 100644 --- a/NativeAppTemplate/UI/Shop Settings/ItemTag List/ItemTagCreateViewModel.swift +++ b/NativeAppTemplate/UI/Shop Settings/ItemTag List/ItemTagCreateViewModel.swift @@ -37,7 +37,7 @@ final class ItemTagCreateViewModel { } var hasInvalidDataName: Bool { - if Utility.isBlank(name) { + if name.isBlank { return true } if name.count > maximumNameLength { diff --git a/NativeAppTemplate/UI/Shop Settings/ShopBasicSettingsViewModel.swift b/NativeAppTemplate/UI/Shop Settings/ShopBasicSettingsViewModel.swift index ffb87db..2447c8b 100644 --- a/NativeAppTemplate/UI/Shop Settings/ShopBasicSettingsViewModel.swift +++ b/NativeAppTemplate/UI/Shop Settings/ShopBasicSettingsViewModel.swift @@ -59,7 +59,7 @@ final class ShopBasicSettingsViewModel { } var hasInvalidDataName: Bool { - if Utility.isBlank(name) { + if name.isBlank { return true } if name.count > maximumNameLength { diff --git a/NativeAppTemplate/Utilities/Utility.swift b/NativeAppTemplate/Utilities/Utility.swift index 9c580e6..89b3c82 100644 --- a/NativeAppTemplate/Utilities/Utility.swift +++ b/NativeAppTemplate/Utilities/Utility.swift @@ -46,16 +46,6 @@ enum Utility { return optionalString ?? "N/A" } - static func isBlank(_ text: String) -> Bool { - let trimmed = text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - return trimmed.isEmpty - } - - static func validateEmail(_ email: String) -> Bool { - let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" - return NSPredicate(format: "SELF MATCHES %@", emailRegex).evaluate(with: email) - } - private static func currentTimeZoneHour() -> (Int, Int) { let secondsFromGmt: Int = TimeZone.current.secondsFromGMT() let hoursFromGmt = (secondsFromGmt / 3_600) diff --git a/NativeAppTemplateTests/Extensions/StringExtensionsTest.swift b/NativeAppTemplateTests/Extensions/StringExtensionsTest.swift index adfcf1b..efe3481 100644 --- a/NativeAppTemplateTests/Extensions/StringExtensionsTest.swift +++ b/NativeAppTemplateTests/Extensions/StringExtensionsTest.swift @@ -3,36 +3,33 @@ // NativeAppTemplate // -import CoreGraphics @testable import NativeAppTemplate import Testing struct StringExtensionsTest { @Test(arguments: [ - ("hello123", true), - ("ABC", true), - ("abc", true), - ("123", true), - ("Hello World", false), - ("hello!", false), - ("hello@world", false), - ("", false) + ("", true), + (" ", true), + ("\n", true), + (" \t\n ", true), + ("hello", false), + (" hello ", false), ]) - func isAlphanumeric(input: String, expected: Bool) { - #expect(input.isAlphanumeric() == expected) + func isBlank(text: String, expected: Bool) { + #expect(text.isBlank == expected) } - @Test - func isAlphanumericIgnoringDiacritics() { - #expect("hello123".isAlphanumeric(ignoreDiacritics: true) == true) - #expect("ABC123".isAlphanumeric(ignoreDiacritics: true) == true) - #expect("hello world".isAlphanumeric(ignoreDiacritics: true) == false) - #expect("".isAlphanumeric(ignoreDiacritics: true) == false) - } - - @Test - func imageGeneration() { - let image = "A".image(size: CGSize(width: 50, height: 50)) - #expect(image != nil) + @Test(arguments: [ + ("test@example.com", true), + ("user.name+tag@domain.co", true), + ("user@domain.com", true), + ("", false), + ("notanemail", false), + ("@domain.com", false), + ("user@", false), + ("user@.com", false), + ]) + func isValidEmail(email: String, expected: Bool) { + #expect(email.isValidEmail == expected) } } diff --git a/NativeAppTemplateTests/UI/App Root/ForgotPasswordViewModelTest.swift b/NativeAppTemplateTests/UI/App Root/ForgotPasswordViewModelTest.swift index 321067c..e8e775f 100644 --- a/NativeAppTemplateTests/UI/App Root/ForgotPasswordViewModelTest.swift +++ b/NativeAppTemplateTests/UI/App Root/ForgotPasswordViewModelTest.swift @@ -22,8 +22,8 @@ struct ForgotPasswordViewModelTest { let email = "" // Simulate the validation logic from the ViewModel - let isBlank = Utility.isBlank(email) - let isInvalid = !Utility.validateEmail(email) + let isBlank = email.isBlank + let isInvalid = !email.isValidEmail let hasInvalidData = isBlank || isInvalid #expect(hasInvalidData == true) @@ -33,8 +33,8 @@ struct ForgotPasswordViewModelTest { func hasInvalidDataWithInvalidEmail() { let email = "invalid-email" - let isBlank = Utility.isBlank(email) - let isInvalid = !Utility.validateEmail(email) + let isBlank = email.isBlank + let isInvalid = !email.isValidEmail let hasInvalidData = isBlank || isInvalid #expect(hasInvalidData == true) @@ -44,8 +44,8 @@ struct ForgotPasswordViewModelTest { func hasInvalidDataWithValidEmail() { let email = "valid@example.com" - let isBlank = Utility.isBlank(email) - let isInvalid = !Utility.validateEmail(email) + let isBlank = email.isBlank + let isInvalid = !email.isValidEmail let hasInvalidData = isBlank || isInvalid #expect(hasInvalidData == false) @@ -54,35 +54,35 @@ struct ForgotPasswordViewModelTest { @Test func isEmailBlankValidation() { // Test blank email detection - #expect(Utility.isBlank("") == true) - #expect(Utility.isBlank(" ") == true) - #expect(Utility.isBlank("test@example.com") == false) + #expect("".isBlank == true) + #expect(" ".isBlank == true) + #expect("test@example.com".isBlank == false) } @Test func isEmailInvalidValidation() { // Test email format validation - #expect(Utility.validateEmail("") == false) - #expect(Utility.validateEmail("invalid") == false) - #expect(Utility.validateEmail("invalid@") == false) - #expect(Utility.validateEmail("@invalid.com") == false) - #expect(Utility.validateEmail("valid@example.com") == true) - #expect(Utility.validateEmail("user+tag@domain.org") == true) + #expect("".isValidEmail == false) + #expect("invalid".isValidEmail == false) + #expect("invalid@".isValidEmail == false) + #expect("@invalid.com".isValidEmail == false) + #expect("valid@example.com".isValidEmail == true) + #expect("user+tag@domain.org".isValidEmail == true) } @Test func emailValidationEdgeCases() { // Test various email formats - #expect(Utility.validateEmail("user.name@domain.com") == true) - #expect(Utility.validateEmail("user+tag@domain.co.uk") == true) - #expect(Utility.validateEmail("user@subdomain.domain.org") == true) - #expect(Utility.validateEmail("123@domain.com") == true) + #expect("user.name@domain.com".isValidEmail == true) + #expect("user+tag@domain.co.uk".isValidEmail == true) + #expect("user@subdomain.domain.org".isValidEmail == true) + #expect("123@domain.com".isValidEmail == true) // Invalid cases - #expect(Utility.validateEmail("user@") == false) - #expect(Utility.validateEmail("@domain.com") == false) - #expect(Utility.validateEmail("user.domain.com") == false) - #expect(Utility.validateEmail("user space@domain.com") == false) + #expect("user@".isValidEmail == false) + #expect("@domain.com".isValidEmail == false) + #expect("user.domain.com".isValidEmail == false) + #expect("user space@domain.com".isValidEmail == false) } @Test diff --git a/NativeAppTemplateTests/UI/App Root/ResendConfirmationInstructionsViewModelTest.swift b/NativeAppTemplateTests/UI/App Root/ResendConfirmationInstructionsViewModelTest.swift index 4fbc066..2c4bbca 100644 --- a/NativeAppTemplateTests/UI/App Root/ResendConfirmationInstructionsViewModelTest.swift +++ b/NativeAppTemplateTests/UI/App Root/ResendConfirmationInstructionsViewModelTest.swift @@ -22,8 +22,8 @@ struct ResendConfirmationViewModelTest { let email = "" // Simulate the validation logic from the ViewModel - let isBlank = Utility.isBlank(email) - let isInvalid = !Utility.validateEmail(email) + let isBlank = email.isBlank + let isInvalid = !email.isValidEmail let hasInvalidData = isBlank || isInvalid #expect(hasInvalidData == true) @@ -33,8 +33,8 @@ struct ResendConfirmationViewModelTest { func hasInvalidDataWithInvalidEmail() { let email = "invalid-email" - let isBlank = Utility.isBlank(email) - let isInvalid = !Utility.validateEmail(email) + let isBlank = email.isBlank + let isInvalid = !email.isValidEmail let hasInvalidData = isBlank || isInvalid #expect(hasInvalidData == true) @@ -44,8 +44,8 @@ struct ResendConfirmationViewModelTest { func hasInvalidDataWithValidEmail() { let email = "valid@example.com" - let isBlank = Utility.isBlank(email) - let isInvalid = !Utility.validateEmail(email) + let isBlank = email.isBlank + let isInvalid = !email.isValidEmail let hasInvalidData = isBlank || isInvalid #expect(hasInvalidData == false) @@ -54,35 +54,35 @@ struct ResendConfirmationViewModelTest { @Test func isEmailBlankValidation() { // Test blank email detection - #expect(Utility.isBlank("") == true) - #expect(Utility.isBlank(" ") == true) - #expect(Utility.isBlank("test@example.com") == false) + #expect("".isBlank == true) + #expect(" ".isBlank == true) + #expect("test@example.com".isBlank == false) } @Test func isEmailInvalidValidation() { // Test email format validation - #expect(Utility.validateEmail("") == false) - #expect(Utility.validateEmail("invalid") == false) - #expect(Utility.validateEmail("invalid@") == false) - #expect(Utility.validateEmail("@invalid.com") == false) - #expect(Utility.validateEmail("valid@example.com") == true) - #expect(Utility.validateEmail("user+tag@domain.org") == true) + #expect("".isValidEmail == false) + #expect("invalid".isValidEmail == false) + #expect("invalid@".isValidEmail == false) + #expect("@invalid.com".isValidEmail == false) + #expect("valid@example.com".isValidEmail == true) + #expect("user+tag@domain.org".isValidEmail == true) } @Test func emailValidationEdgeCases() { // Test various email formats - #expect(Utility.validateEmail("user.name@domain.com") == true) - #expect(Utility.validateEmail("user+tag@domain.co.uk") == true) - #expect(Utility.validateEmail("user@subdomain.domain.org") == true) - #expect(Utility.validateEmail("123@domain.com") == true) + #expect("user.name@domain.com".isValidEmail == true) + #expect("user+tag@domain.co.uk".isValidEmail == true) + #expect("user@subdomain.domain.org".isValidEmail == true) + #expect("123@domain.com".isValidEmail == true) // Invalid cases - #expect(Utility.validateEmail("user@") == false) - #expect(Utility.validateEmail("@domain.com") == false) - #expect(Utility.validateEmail("user.domain.com") == false) - #expect(Utility.validateEmail("user space@domain.com") == false) + #expect("user@".isValidEmail == false) + #expect("@domain.com".isValidEmail == false) + #expect("user.domain.com".isValidEmail == false) + #expect("user space@domain.com".isValidEmail == false) } @Test diff --git a/NativeAppTemplateTests/Utilities/UtilityTest.swift b/NativeAppTemplateTests/Utilities/UtilityTest.swift deleted file mode 100644 index 2c7e3c9..0000000 --- a/NativeAppTemplateTests/Utilities/UtilityTest.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// UtilityTest.swift -// NativeAppTemplate -// - -import Foundation -@testable import NativeAppTemplate -import Testing - -struct UtilityTest { - @Test(arguments: [ - ("", true), - (" ", true), - ("\n", true), - (" \t\n ", true), - ("hello", false), - (" hello ", false) - ]) - func isBlank(text: String, expected: Bool) { - #expect(Utility.isBlank(text) == expected) - } - - @Test(arguments: [ - ("test@example.com", true), - ("user.name+tag@domain.co", true), - ("user@domain.com", true), - ("", false), - ("notanemail", false), - ("@domain.com", false), - ("user@", false), - ("user@.com", false) - ]) - func validateEmail(email: String, expected: Bool) { - #expect(Utility.validateEmail(email) == expected) - } - -} diff --git a/README.md b/README.md index af0c60c..62411af 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,8 @@ git restore --source=HEAD --staged --worktree NativeAppTemplate.xcodeproj/xcshar Debug builds read these at launch via `ProcessInfo.processInfo.environment` in `Constants.swift`; when unset, they fall back to the production defaults (`https://api.nativeapptemplate.com`). Release builds always use the production defaults. +In practice, only Xcode injects these env vars (via the scheme), so a Debug build launched any other way — tapped from the Home Screen (SpringBoard), opened on a physical device after Xcode disconnects, etc. — sees them unset and falls through to the production defaults. The hardcoded fallbacks are what keep the app working without Xcode in the loop. + ## SwiftLint SwiftLint runs as part of the build process in Xcode, and errors/warnings are surfaced in Xcode as well. Please ensure that you run SwiftLint before submitting a pull request.