diff --git a/README.md b/README.md index 36f016c..1828c9a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# iMessageClone - +# iMessage Clone written in Swift diff --git a/Training.xcodeproj/project.pbxproj b/Training.xcodeproj/project.pbxproj new file mode 100644 index 0000000..857abe9 --- /dev/null +++ b/Training.xcodeproj/project.pbxproj @@ -0,0 +1,351 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 193322BA28BABE4C00ECECB0 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193322B928BABE4C00ECECB0 /* ChatModel.swift */; }; + 193322BC28BAC60900ECECB0 /* Date+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193322BB28BAC60900ECECB0 /* Date+Helpers.swift */; }; + 193322C028BAC92F00ECECB0 /* ChatsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193322BF28BAC92F00ECECB0 /* ChatsViewModel.swift */; }; + 193322C228BACCDA00ECECB0 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193322C128BACCDA00ECECB0 /* ChatView.swift */; }; + C07D316878F6AA0353D447AC /* ChatRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07D3D7C6B2D53FDE2D935E0 /* ChatRow.swift */; }; + C07D3754C665BEFBB6512D42 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = C07D3595FE6195CC1E0EF874 /* .gitignore */; }; + C07D39C9FDC07D92EA4484E8 /* TrainingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07D3FD47910198196A071C6 /* TrainingApp.swift */; }; + C07D3B52F9476D6CBBDA9F86 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07D39E44D0D6D8CEE7B119B /* ContentView.swift */; }; + C07D3CE7CD14489A53B34593 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C07D3AC94044DC0078A9C96B /* Preview Assets.xcassets */; }; + C07D3EC1DF66197FCF10B04A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C07D323607E19080E00F500C /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 193322B928BABE4C00ECECB0 /* ChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModel.swift; sourceTree = ""; }; + 193322BB28BAC60900ECECB0 /* Date+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Helpers.swift"; sourceTree = ""; }; + 193322BF28BAC92F00ECECB0 /* ChatsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsViewModel.swift; sourceTree = ""; }; + 193322C128BACCDA00ECECB0 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; + C07D306AC5D69BF90D6B7837 /* Training.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Training.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C07D323607E19080E00F500C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + C07D3595FE6195CC1E0EF874 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = file.gitignore; path = .gitignore; sourceTree = ""; }; + C07D39E44D0D6D8CEE7B119B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + C07D3AC94044DC0078A9C96B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + C07D3D7C6B2D53FDE2D935E0 /* ChatRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatRow.swift; sourceTree = ""; }; + C07D3FD47910198196A071C6 /* TrainingApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrainingApp.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C07D35D9F1DE858A42F9EA17 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C07D31B1964ABAA1A810E3AF /* Training */ = { + isa = PBXGroup; + children = ( + C07D323607E19080E00F500C /* Assets.xcassets */, + C07D3D1BECC205B3E5399B3A /* Preview Content */, + C07D3FD47910198196A071C6 /* TrainingApp.swift */, + C07D39E44D0D6D8CEE7B119B /* ContentView.swift */, + C07D3D7C6B2D53FDE2D935E0 /* ChatRow.swift */, + 193322B928BABE4C00ECECB0 /* ChatModel.swift */, + 193322BB28BAC60900ECECB0 /* Date+Helpers.swift */, + 193322BF28BAC92F00ECECB0 /* ChatsViewModel.swift */, + 193322C128BACCDA00ECECB0 /* ChatView.swift */, + ); + path = Training; + sourceTree = ""; + }; + C07D371AFE61FD792BFD1B66 /* Products */ = { + isa = PBXGroup; + children = ( + C07D306AC5D69BF90D6B7837 /* Training.app */, + ); + name = Products; + sourceTree = ""; + }; + C07D39A49118DB20FD6FC2F6 = { + isa = PBXGroup; + children = ( + C07D371AFE61FD792BFD1B66 /* Products */, + C07D31B1964ABAA1A810E3AF /* Training */, + C07D3595FE6195CC1E0EF874 /* .gitignore */, + ); + sourceTree = ""; + }; + C07D3D1BECC205B3E5399B3A /* Preview Content */ = { + isa = PBXGroup; + children = ( + C07D3AC94044DC0078A9C96B /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C07D358237A92B11CC514E20 /* Training */ = { + isa = PBXNativeTarget; + buildConfigurationList = C07D3105791B837A22B25329 /* Build configuration list for PBXNativeTarget "Training" */; + buildPhases = ( + C07D3E323FBC052ED49F708A /* Sources */, + C07D35D9F1DE858A42F9EA17 /* Frameworks */, + C07D3697D5A54CFD2B988BB1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Training; + productName = Training; + productReference = C07D306AC5D69BF90D6B7837 /* Training.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C07D3850411372B23AADAF30 /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = C07D319DD8CBD07A95D69AAC /* Build configuration list for PBXProject "Training" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = C07D39A49118DB20FD6FC2F6; + productRefGroup = C07D371AFE61FD792BFD1B66 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C07D358237A92B11CC514E20 /* Training */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C07D3697D5A54CFD2B988BB1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C07D3EC1DF66197FCF10B04A /* Assets.xcassets in Resources */, + C07D3CE7CD14489A53B34593 /* Preview Assets.xcassets in Resources */, + C07D3754C665BEFBB6512D42 /* .gitignore in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C07D3E323FBC052ED49F708A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 193322C028BAC92F00ECECB0 /* ChatsViewModel.swift in Sources */, + C07D39C9FDC07D92EA4484E8 /* TrainingApp.swift in Sources */, + C07D3B52F9476D6CBBDA9F86 /* ContentView.swift in Sources */, + C07D316878F6AA0353D447AC /* ChatRow.swift in Sources */, + 193322C228BACCDA00ECECB0 /* ChatView.swift in Sources */, + 193322BA28BABE4C00ECECB0 /* ChatModel.swift in Sources */, + 193322BC28BAC60900ECECB0 /* Date+Helpers.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C07D3289C94B96864CE471AD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Training/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = mrtuxa.Training; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C07D35A68F4E4FB5617ECC75 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + 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; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 15.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C07D36637BD8CF8F50D1DF55 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Training/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = mrtuxa.Training; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + C07D3EBF265CF8C9E4BBA01B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + 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"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = 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 = 15.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C07D3105791B837A22B25329 /* Build configuration list for PBXNativeTarget "Training" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C07D3289C94B96864CE471AD /* Debug */, + C07D36637BD8CF8F50D1DF55 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C07D319DD8CBD07A95D69AAC /* Build configuration list for PBXProject "Training" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C07D35A68F4E4FB5617ECC75 /* Debug */, + C07D3EBF265CF8C9E4BBA01B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C07D3850411372B23AADAF30 /* Project object */; +} diff --git a/Training.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Training.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Training.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Training.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Training.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Training.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Training/Assets.xcassets/AccentColor.colorset/Contents.json b/Training/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..0afb3cf --- /dev/null +++ b/Training/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors": [ + { + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/AppIcon.appiconset/Contents.json b/Training/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..faeb2bb --- /dev/null +++ b/Training/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images": [ + { + "idiom": "iphone", + "scale": "2x", + "size": "20x20" + }, + { + "idiom": "iphone", + "scale": "3x", + "size": "20x20" + }, + { + "idiom": "iphone", + "scale": "2x", + "size": "29x29" + }, + { + "idiom": "iphone", + "scale": "3x", + "size": "29x29" + }, + { + "idiom": "iphone", + "scale": "2x", + "size": "40x40" + }, + { + "idiom": "iphone", + "scale": "3x", + "size": "40x40" + }, + { + "idiom": "iphone", + "scale": "2x", + "size": "60x60" + }, + { + "idiom": "iphone", + "scale": "3x", + "size": "60x60" + }, + { + "idiom": "ipad", + "scale": "1x", + "size": "20x20" + }, + { + "idiom": "ipad", + "scale": "2x", + "size": "20x20" + }, + { + "idiom": "ipad", + "scale": "1x", + "size": "29x29" + }, + { + "idiom": "ipad", + "scale": "2x", + "size": "29x29" + }, + { + "idiom": "ipad", + "scale": "1x", + "size": "40x40" + }, + { + "idiom": "ipad", + "scale": "2x", + "size": "40x40" + }, + { + "idiom": "ipad", + "scale": "1x", + "size": "76x76" + }, + { + "idiom": "ipad", + "scale": "2x", + "size": "76x76" + }, + { + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" + }, + { + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/Colors/Background.colorset/Contents.json b/Training/Assets.xcassets/Colors/Background.colorset/Contents.json new file mode 100644 index 0000000..ca5ba7c --- /dev/null +++ b/Training/Assets.xcassets/Colors/Background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "1.000", + "red": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "1.000", + "red": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/Colors/Contents.json b/Training/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..74d6a72 --- /dev/null +++ b/Training/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/Colors/Icon.colorset/Contents.json b/Training/Assets.xcassets/Colors/Icon.colorset/Contents.json new file mode 100644 index 0000000..ca5ba7c --- /dev/null +++ b/Training/Assets.xcassets/Colors/Icon.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "1.000", + "red": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "1.000", + "red": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/Colors/Text.colorset/Contents.json b/Training/Assets.xcassets/Colors/Text.colorset/Contents.json new file mode 100644 index 0000000..ca5ba7c --- /dev/null +++ b/Training/Assets.xcassets/Colors/Text.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors": [ + { + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "1.000", + "red": "1.000" + } + }, + "idiom": "universal" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "color": { + "color-space": "srgb", + "components": { + "alpha": "1.000", + "blue": "1.000", + "green": "1.000", + "red": "1.000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/Contents.json b/Training/Assets.xcassets/Contents.json new file mode 100644 index 0000000..74d6a72 --- /dev/null +++ b/Training/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/Unbenannt-1.imageset/Contents.json b/Training/Assets.xcassets/Unbenannt-1.imageset/Contents.json new file mode 100644 index 0000000..67b83bc --- /dev/null +++ b/Training/Assets.xcassets/Unbenannt-1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images": [ + { + "filename": "Unbenannt-1.jpeg", + "idiom": "universal", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/Unbenannt-1.imageset/Unbenannt-1.jpeg b/Training/Assets.xcassets/Unbenannt-1.imageset/Unbenannt-1.jpeg new file mode 100644 index 0000000..ac498a8 Binary files /dev/null and b/Training/Assets.xcassets/Unbenannt-1.imageset/Unbenannt-1.jpeg differ diff --git a/Training/Assets.xcassets/mrtuxa.imageset/Contents.json b/Training/Assets.xcassets/mrtuxa.imageset/Contents.json new file mode 100644 index 0000000..3a3bb2a --- /dev/null +++ b/Training/Assets.xcassets/mrtuxa.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images": [ + { + "filename": "mrtuxa.png", + "idiom": "universal", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/Assets.xcassets/mrtuxa.imageset/mrtuxa.png b/Training/Assets.xcassets/mrtuxa.imageset/mrtuxa.png new file mode 100644 index 0000000..899d2df Binary files /dev/null and b/Training/Assets.xcassets/mrtuxa.imageset/mrtuxa.png differ diff --git a/Training/ChatModel.swift b/Training/ChatModel.swift new file mode 100644 index 0000000..2a858ec --- /dev/null +++ b/Training/ChatModel.swift @@ -0,0 +1,61 @@ +// +// ChatModel.swift +// Training +// +// Created by mrtuxa on 27.08.22. +// + +import Foundation + +struct Chat: Identifiable { + var id: UUID { + person.id + } + let person: Person + var messages: [Message] + var hasUnreadMessage = false +} + +struct Person: Identifiable { + let id = UUID() + let name: String + let imgString: String +} + + +struct Message: Identifiable { + enum MessageType { + case Sent, Received + } + + let id = UUID() + let date: Date + let text: String + let type: MessageType + + init(_ text: String, type: MessageType, date: Date) { + self.date = date + self.type = type + self.text = text + } + + init(_ text: String, type: MessageType) { + self.init(text, type: type, date: Date()) + + } +} + +extension Chat { + + static let sampleChat = [ + Chat(person: Person(name: "Josh", imgString: "Unbenannt-1"), messages: [ + Message("Hey Josh", type: .Sent, date: Date(timeIntervalSinceNow: -86400 * 3)), + Message("Ich entwickle gerade einen Whatsapp Clone, aber es ist schon schwer Konversation zu faken. Kannst du mir helfen", type: .Sent, date: Date(timeIntervalSinceNow: -86400 * 3)), + ], hasUnreadMessage: true), + Chat(person: Person(name: "Tobi", imgString: "Unbenannt-1"), messages: [ + Message("Heute Fortnite", type: .Sent, date: Date(timeIntervalSinceNow: -86400 * 3)), + Message("Nein Valo", type: .Received, date: Date()), + ], hasUnreadMessage: false) + ] + +} diff --git a/Training/ChatRow.swift b/Training/ChatRow.swift new file mode 100644 index 0000000..f6c9996 --- /dev/null +++ b/Training/ChatRow.swift @@ -0,0 +1,60 @@ +// +// Created by mrtuxa on 27.08.22. +// + +import SwiftUI +import Foundation + + +struct ChatRow: View { + + let chat: Chat + + var body: some View { + HStack(spacing: 20) { + Image(chat.person.imgString) + .resizable() + .frame(width: 70, height: 70) + .clipShape(Circle()) + + ZStack { + VStack(alignment: .leading, spacing: 5) { + HStack { + Text(chat.person.name) + .bold() + } + + VStack(alignment: .leading) { + Text(chat.messages.last?.date.descriptiveString() ?? "").multilineTextAlignment(.center) + } + + HStack { + Text(chat.messages.last?.text ?? "") + .lineLimit(1) + .frame(height: 50, alignment: .top) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.trailing, 25) + } + } + Circle() + .foregroundColor(chat.hasUnreadMessage ? .blue : .clear) + .frame(width: 18, height: 18) + .frame(maxWidth: .infinity, alignment: .trailing) + } + } + .frame(height: 80) + } +} + +struct ChatRow_Previews: PreviewProvider { + static var previews: some View { + ChatRow(chat: Chat.sampleChat[0]) + } +} + +func CurrentDate() -> String { + let date = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd.MM.yyyy" + return dateFormatter.string(from: date) +} diff --git a/Training/ChatView.swift b/Training/ChatView.swift new file mode 100644 index 0000000..738a264 --- /dev/null +++ b/Training/ChatView.swift @@ -0,0 +1,177 @@ +// +// ChatView.swift +// Training +// +// Created by mrtuxa on 28.08.22. +// + +import SwiftUI + + +struct ChatView: View { + + @EnvironmentObject var viewModel: ChatsViewModel + let chat: Chat + + @State private var text = "" + @FocusState private var isFocused + + @State private var messageIDToScroll: UUID? + + var navBarLeadingBtn: some View { + Button(action: {}) { + HStack { + Image(chat.person.imgString) + .resizable() + .frame(width: 40, height: 40) + .clipShape(Circle()) + + Text(chat.person.name).bold() + }.foregroundColor(.black) + } + } + + var navBarTrailingBtn: some View { + HStack { + Button(action: {}) { + Image(systemName: "video") + } + Button(action: {}) { + Image(systemName: "phone") + } + } + } + + var body: some View { + VStack { + GeometryReader { reader in + ScrollView { + ScrollViewReader { scrollReader in + getMessageView(viewWidth: reader.size.width) + .padding(.horizontal) + .onChange(of: messageIDToScroll) { _ in + if let messageID = messageIDToScroll { + scrollTo(messageID: messageID, shouldAnimate: true, scrollReader: scrollReader) + } + } + .onAppear { + if let messageID = chat.messages.last?.id { + scrollTo(messageID: messageID, anchor: .bottom, shouldAnimate: false, scrollReader: scrollReader) + } + } + } + + } + } + .padding(.bottom, 5) + + toolbarView() + } + .padding(.top, 1) + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(leading: navBarLeadingBtn, trailing: navBarTrailingBtn) + .onAppear { + viewModel.markAsUnread(false, chat: chat) + } + } + + + + func scrollTo(messageID: UUID, anchor: UnitPoint? = nil, shouldAnimate: Bool, scrollReader: ScrollViewProxy) { + DispatchQueue.main.async { + withAnimation(shouldAnimate ? Animation.easeIn : nil) { + scrollReader.scrollTo(messageID, anchor: anchor) + } + } + } + + + func toolbarView() -> some View { + VStack { + let height: CGFloat = 37 + HStack { + TextField("Message ...", text: $text) + .padding(.horizontal, 10) + .frame(height: height) + .background(Color(UIColor.lightGray)) + .clipShape(RoundedRectangle(cornerRadius: 13)) + .focused($isFocused) + + Button(action: sendMessage) { + Image(systemName: "paperplane.fill") + .foregroundColor(.white) + .frame(width: height, height: height) + .background( + Circle() + .foregroundColor(text.isEmpty ? .blue : .green) + ) + }.disabled(text.isEmpty) + }.frame(height: height) + } + .padding(.vertical) + .padding(.horizontal) + .background(.thickMaterial) + } + + func sendMessage() { + if let message = viewModel.sendMessage(text, in: chat) { + text = "" + messageIDToScroll = message.id + } + } + + + let columns = [GridItem(.flexible(minimum: 10))] + + func getMessageView(viewWidth: CGFloat) -> some View { + LazyVGrid(columns: columns, spacing: 0, pinnedViews: [.sectionHeaders]) { + let sectionMessages = viewModel.getSectionMessages(for: chat) + ForEach(sectionMessages.indices, id: \.self) { sectionIndex in + let messages = sectionMessages[sectionIndex] + Section(header: sectionHeader(firstMessage: messages.first!)) { + ForEach(messages) { message in + let isReceived = message.type == .Received + HStack { + ZStack { + Text(message.text) + .padding(.horizontal) + .padding(.vertical, 12) + .background(isReceived ? Color.black.opacity(0.2) : .green.opacity(0.9)) + .cornerRadius(13) + } + .background(Color.blue) + .padding(.vertical) + .frame(width: viewWidth * 0.7, alignment: isReceived ? .leading : .trailing) + + } + .frame(maxWidth: .infinity, alignment: isReceived ? .leading : .trailing) + .id(message.id) // important for automatic scrolling later! + } + } + } + } + } + + func sectionHeader(firstMessage message: Message) -> some View { + ZStack { + Text(message.date.descriptiveString(dateStyle: .medium)) + .foregroundColor(.white) + .font(.system(size: 14, weight: .regular)) + .frame(width: 120) + .padding(.vertical, 5) + .background(Capsule().foregroundColor(.cyan)) + } + .padding(.vertical, 5) + .frame(maxWidth: .infinity) + } +} + + +struct ChatView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + ChatView(chat: Chat.sampleChat[0]) + .environmentObject(ChatsViewModel()) + } + } +} diff --git a/Training/ChatsViewModel.swift b/Training/ChatsViewModel.swift new file mode 100644 index 0000000..cb26ae6 --- /dev/null +++ b/Training/ChatsViewModel.swift @@ -0,0 +1,69 @@ +// +// ChatsViewModel.swift +// Training +// +// Created by mrtuxa on 27.08.22. +// + +import Foundation + + +class ChatsViewModel: ObservableObject { + + @Published var chats = Chat.sampleChat + + func getSortedFilteredChats(query: String) -> [Chat] { + let sortedChats = chats.sorted { + guard let date1 = $0.messages.last?.date else { + return false + } + guard let date2 = $1.messages.last?.date else { + return false + } + return date1 > date2 + } + + if query == "" { + return sortedChats + } + return sortedChats.filter { + $0.person.name.lowercased().contains(query.lowercased()) + } + } + + func getSectionMessages(for chat: Chat) -> [[Message]] { + var res = [[Message]]() + var tmp = [Message]() + for message in chat.messages { + if let firstMessage = tmp.first { + let daysBetween = firstMessage.date.daysBetween(date: message.date) + if daysBetween >= 1 { + res.append(tmp) + tmp.removeAll() + tmp.append(message) + } else { + tmp.append(message) + } + } else { + tmp.append(message) + } + } + res.append(tmp) + return res + } + + func markAsUnread(_ newValue: Bool, chat: Chat) { + if let index = chat.messages.firstIndex(where: { $0.id == chat.id }) { + chats[index].hasUnreadMessage = newValue + } + } + + func sendMessage(_ text: String, in chat: Chat) -> Message? { + if let index = chats.firstIndex { $0.id == chat.id } { + let message = Message(text, type: .Sent) + chats[index].messages.append(message) + return message + } + return nil + } +} diff --git a/Training/ContentView.swift b/Training/ContentView.swift new file mode 100644 index 0000000..20c91d7 --- /dev/null +++ b/Training/ContentView.swift @@ -0,0 +1,69 @@ +// +// ContentView.swift +// Training +// +// Created by mrtuxa on 27.08.22. +// +// + +import SwiftUI + + +struct ContentView: View { + + @StateObject var viewModel = ChatsViewModel() + + @State private var query = "" + + + + var body: some View { + NavigationView { + List { + ForEach(viewModel.getSortedFilteredChats(query: query)) { chat in + + ZStack { + + ChatRow(chat: chat) + + NavigationLink(destination: { + ChatView(chat: chat) + .environmentObject(viewModel) + }) { + EmptyView() + } + .buttonStyle(PlainButtonStyle()) + .frame(width: 0) + .opacity(0) + } + .swipeActions(edge: .leading, allowsFullSwipe: true) { + Button(action: { + viewModel.markAsUnread(!chat.hasUnreadMessage, chat: chat) + }) { + if chat.hasUnreadMessage { + Label("Read", systemImage: "text.bubble") + } else { + Label("Unread", systemImage: "circle.fill") + } + } + .tint(.blue) + } + } + } + .listStyle(PlainListStyle()) + .searchable(text: $query) + .navigationTitle("Chats") + .navigationBarItems(trailing: Button(action: {}) { + Image(systemName: "square.and.pencil") + }) + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} + + diff --git a/Training/Date+Helpers.swift b/Training/Date+Helpers.swift new file mode 100644 index 0000000..e103fa8 --- /dev/null +++ b/Training/Date+Helpers.swift @@ -0,0 +1,40 @@ +// +// Date+Helpers.swift +// Training +// +// Created by mrtuxa on 27.08.22. +// + +import Foundation + +extension Date { + + func descriptiveString(dateStyle: DateFormatter.Style = .short) -> String { + + let formatter = DateFormatter() + formatter.dateStyle = dateStyle + + let daysBetween = self.daysBetween(date: Date()) + + if daysBetween == 0 { + return "Today" + } else if daysBetween == 1 { + return "Yesterday" + } else if daysBetween < 5 { + let weekdayIndex = Calendar.current.component(.weekday, from: self) - 1 + return formatter.weekdaySymbols[weekdayIndex] + } + return formatter.string(from: self) + } + + func daysBetween(date: Date) -> Int { + let calendar = Calendar.current + let date1 = calendar.startOfDay(for: self) + let date2 = calendar.startOfDay(for: date) + if let daysBetween = calendar.dateComponents([.day], from: date1, to: date2).day { + return daysBetween + } + return 0 + } + +} diff --git a/Training/Preview Content/Preview Assets.xcassets/Contents.json b/Training/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..74d6a72 --- /dev/null +++ b/Training/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/Training/TrainingApp.swift b/Training/TrainingApp.swift new file mode 100644 index 0000000..76c4e03 --- /dev/null +++ b/Training/TrainingApp.swift @@ -0,0 +1,18 @@ +// +// TrainingApp.swift +// Training +// +// Created by mrtuxa on 27.08.22. +// +// + +import SwiftUI + +@main +struct TrainingApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +}