master
mrtuxa 2022-11-24 01:29:43 +01:00
parent 13c6dd1979
commit 82e8dd991a
23 changed files with 1144 additions and 2 deletions

View File

@ -1,2 +1 @@
# iMessageClone
# iMessage Clone written in Swift

View File

@ -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 = "<group>"; };
193322BB28BAC60900ECECB0 /* Date+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Helpers.swift"; sourceTree = "<group>"; };
193322BF28BAC92F00ECECB0 /* ChatsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsViewModel.swift; sourceTree = "<group>"; };
193322C128BACCDA00ECECB0 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
C07D3595FE6195CC1E0EF874 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = file.gitignore; path = .gitignore; sourceTree = "<group>"; };
C07D39E44D0D6D8CEE7B119B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
C07D3AC94044DC0078A9C96B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
C07D3D7C6B2D53FDE2D935E0 /* ChatRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatRow.swift; sourceTree = "<group>"; };
C07D3FD47910198196A071C6 /* TrainingApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrainingApp.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
C07D371AFE61FD792BFD1B66 /* Products */ = {
isa = PBXGroup;
children = (
C07D306AC5D69BF90D6B7837 /* Training.app */,
);
name = Products;
sourceTree = "<group>";
};
C07D39A49118DB20FD6FC2F6 = {
isa = PBXGroup;
children = (
C07D371AFE61FD792BFD1B66 /* Products */,
C07D31B1964ABAA1A810E3AF /* Training */,
C07D3595FE6195CC1E0EF874 /* .gitignore */,
);
sourceTree = "<group>";
};
C07D3D1BECC205B3E5399B3A /* Preview Content */ = {
isa = PBXGroup;
children = (
C07D3AC94044DC0078A9C96B /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
/* 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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,11 @@
{
"colors": [
{
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
{
"info": {
"author": "xcode",
"version": 1
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
{
"info": {
"author": "xcode",
"version": 1
}
}

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

61
Training/ChatModel.swift Normal file
View File

@ -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)
]
}

60
Training/ChatRow.swift Normal file
View File

@ -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)
}

177
Training/ChatView.swift Normal file
View File

@ -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())
}
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
{
"info": {
"author": "xcode",
"version": 1
}
}

View File

@ -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()
}
}
}