├── .gitignore
├── AddaMeIOS.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ ├── AddaMeIOS.xcscheme
│ └── AddaMeIOSUITests.xcscheme
├── AddaMeIOS
├── API
│ ├── ChatAPI
│ │ ├── ConversationAPI.swift
│ │ └── MessageAPI.swift
│ ├── ContactAPI
│ │ └── ContactAPI.swift
│ ├── EventAPI
│ │ ├── EventAPI.swift
│ │ └── EventPlaceAPI.swift
│ └── UserAPI
│ │ ├── AttachmentAPI.swift
│ │ ├── AuthAPI.swift
│ │ ├── DeviceAPI.swift
│ │ ├── RefreshTokenAPI.swift
│ │ └── UserAPI.swift
├── AddaMeIOS.entitlements
├── App
│ ├── AppDelegate+PKPushRegistryDelegate.swift
│ ├── AppDelegate.swift
│ ├── AppState.swift
│ ├── AppTabView.swift
│ ├── Persistence.swift
│ └── SceneDelegate.swift
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ ├── Icon-App-83.5x83.5@2x.png
│ │ └── ItunesArtwork@2x.png
│ ├── Avatar.imageset
│ │ ├── Avatar.png
│ │ ├── Avatar@2x.png
│ │ ├── Avatar@3x.png
│ │ └── Contents.json
│ ├── Color.colorset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── bg.colorset
│ │ └── Contents.json
│ └── iTunesArtwork.imageset
│ │ ├── Contents.json
│ │ ├── iTunesArtwork@1x.png
│ │ ├── iTunesArtwork@2x.png
│ │ └── iTunesArtwork@3x.png
├── Auth
│ ├── AuthProfileView.swift
│ ├── AuthView.swift
│ ├── AuthViewModel.swift
│ ├── HUDProgressView.swift
│ ├── LoginAndVerificationResponse.swift
│ └── RootView.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Chat
│ ├── Authenticator.swift
│ ├── ChatBottomView.swift
│ ├── ChatOutGoingEvent.swift
│ ├── ChatRoomView.swift
│ ├── ChatRow.swift
│ └── WebSocketViewModel.swift
├── Configs
│ └── Development.xcconfig
├── Conversation
│ ├── ConversationList.swift
│ ├── ConversationRow.swift
│ └── ConversationViewModel.swift
├── CoreDataEntities
│ ├── ContactEntity+CoreDataClass.swift
│ ├── ContactEntity+CoreDataProperties.swift
│ └── ContactEntityExtension.swift
├── CoreDataModels
│ ├── AddaModel.xcdatamodeld
│ │ └── AddaModel.xcdatamodel
│ │ │ └── contents
│ └── Contacts.xcdatamodeld
│ │ └── Contacts.xcdatamodel
│ │ └── contents
├── Environment.swift
├── Event
│ ├── ChatDataHandler.swift
│ ├── EventDetail.swift
│ ├── EventForm.swift
│ ├── EventList.swift
│ ├── EventRow.swift
│ └── EventViewModel.swift
├── ExprimentViews
│ ├── Navigation_WithList.swift
│ ├── TabBarTopView.swift
│ └── VarticalExample.swift
├── Extension
│ ├── Array+RemoveDuplicate.swift
│ ├── AssetExtractor.swift
│ ├── Codable+Extension.swift
│ ├── DataExtension.swift
│ ├── Date
│ │ ├── Date+Extension.swift
│ │ └── DateFormatter+Extension.swift
│ ├── Image+Compress.swift
│ ├── Publishers+Keyboard.swift
│ ├── RangeReplaceableCollection+AppendIfNotContains.swift
│ ├── String+Extension.swift
│ ├── String
│ │ ├── String+CodingKey.swift
│ │ ├── String+Data.swift
│ │ ├── String+Date.swift
│ │ └── String+ReplaceCharcters.swift
│ ├── UIApplicationKeyboardOnDragGestureExtension.swift
│ ├── UIImageView+Extension.swift
│ └── View
│ │ └── View+Extension+HideRowSeparatorModifier.swift
├── Helper
│ ├── AppUserDefaults.swift
│ ├── AwsS3Manager.swift
│ ├── Bundle+Decode.swift
│ ├── CoreDataHelpers
│ │ ├── ManagedModel.swift
│ │ └── NSManagedObjectContext+Extensions.swift
│ ├── DateHelper.swift
│ ├── Image
│ │ ├── AsyncImage.swift
│ │ ├── EnvironmentValues+ImageCache.swift
│ │ ├── ImageCache.swift
│ │ ├── ImageDraw.swift
│ │ ├── ImageLoader.swift
│ │ └── ImagePicker.swift
│ ├── Keyboard
│ │ ├── AdaptsToSoftwareKeyboard.swift
│ │ ├── KeyboardAdaptive.swift
│ │ └── UIResponder+Current.swift
│ ├── KeychainService.swift
│ ├── ModalView.swift
│ ├── MongoDBObjectID.swift
│ ├── SwiftUIFormHelper
│ │ ├── KeyboardDismissModifier.swift
│ │ └── KeyboardOffsetModifier.swift
│ └── View
│ │ └── LazyView.swift
├── Info.plist
├── Map
│ ├── LocationManager.swift
│ ├── LocationQuery.swift
│ ├── LocationSearchService.swift
│ ├── MapSearchView.swift
│ ├── MapView.swift
│ ├── MapViewModel.swift
│ ├── MapViewUI.swift
│ └── SearchBar.swift
├── Models
│ ├── Attachment.swift
│ ├── Chat
│ │ └── ChatMessage.swift
│ ├── Contact
│ │ └── Contact.swift
│ ├── Conversation
│ │ ├── Conversation.swift
│ │ └── LastMessage.swift
│ ├── DemoData.swift
│ ├── Event
│ │ └── Event.swift
│ ├── EventPlace
│ │ └── EventPlace.swift
│ ├── FetachDataFromJsonFile.swift
│ └── User
│ │ └── CurrentUser.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ ├── Contents.json
│ │ └── avatar.imageset
│ │ ├── Contents.json
│ │ └── avatar.jpg
├── Resources
│ ├── chatResponseData.json
│ ├── contacts.json
│ ├── conversationResponseData.json
│ └── eventResponseData.json
├── Services
│ └── SystemServices.swift
├── Settings
│ └── SettingsView.swift
├── Shape+Extension.swift
└── User
│ ├── ContactRow.swift
│ ├── ContactViewModel.swift
│ ├── ContactsView.swift
│ ├── DeviceViewModel.swift
│ ├── ProfileView.swift
│ ├── TermsAndPrivacyWebView.swift
│ ├── TermsWebView.swift
│ └── UserViewModel.swift
├── AddaMeIOSUITests
├── AddaMeIOSUITests.swift
└── Info.plist
├── Gemfile
├── Gemfile.lock
├── PhoneNumberMetadata.json
├── README.md
└── fastlane
├── Appfile
├── Deliverfile
├── Fastfile
├── Precheckfile
├── README.md
├── Snapfile
├── SnapshotHelper.swift
├── metadata
├── copyright.txt
├── en-US
│ ├── apple_tv_privacy_policy.txt
│ ├── description.txt
│ ├── keywords.txt
│ ├── marketing_url.txt
│ ├── name.txt
│ ├── privacy_url.txt
│ ├── promotional_text.txt
│ ├── release_notes.txt
│ ├── subtitle.txt
│ └── support_url.txt
├── primary_category.txt
├── primary_first_sub_category.txt
├── primary_second_sub_category.txt
├── review_information
│ ├── demo_password.txt
│ ├── demo_user.txt
│ ├── email_address.txt
│ ├── first_name.txt
│ ├── last_name.txt
│ ├── notes.txt
│ └── phone_number.txt
├── secondary_category.txt
├── secondary_first_sub_category.txt
└── secondary_second_sub_category.txt
└── screenshots
├── Framefile.json
├── README.txt
├── background.jpg
├── en-US
└── title.strings
├── fonts
└── Chalkduster.ttf
└── screenshots.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/swift,xcode,cocoapods
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode,cocoapods
3 |
4 | ### CocoaPods ###
5 | ## CocoaPods GitIgnore Template
6 |
7 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing
8 | # - Also handy if you have a large number of dependant pods
9 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE
10 | Pods/
11 |
12 | ### Swift ###
13 | # Xcode
14 | #
15 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
16 |
17 | ## User settings
18 | xcuserdata/
19 |
20 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
21 | *.xcscmblueprint
22 | *.xccheckout
23 |
24 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
25 | build/
26 | DerivedData/
27 | *.moved-aside
28 | *.pbxuser
29 | !default.pbxuser
30 | *.mode1v3
31 | !default.mode1v3
32 | *.mode2v3
33 | !default.mode2v3
34 | *.perspectivev3
35 | !default.perspectivev3
36 |
37 | ## Obj-C/Swift specific
38 | *.hmap
39 |
40 | ## App packaging
41 | *.ipa
42 | *.dSYM.zip
43 | *.dSYM
44 |
45 | ## Playgrounds
46 | timeline.xctimeline
47 | playground.xcworkspace
48 |
49 | # Swift Package Manager
50 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
51 | # Packages/
52 | # Package.pins
53 | # Package.resolved
54 | # *.xcodeproj
55 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
56 | # hence it is not needed unless you have added a package configuration file to your project
57 | # .swiftpm
58 |
59 | .build/
60 |
61 | # CocoaPods
62 | # We recommend against adding the Pods directory to your .gitignore. However
63 | # you should judge for yourself, the pros and cons are mentioned at:
64 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
65 | # Pods/
66 | # Add this line if you want to avoid checking in source code from the Xcode workspace
67 | # *.xcworkspace
68 |
69 | # Carthage
70 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
71 | # Carthage/Checkouts
72 |
73 | Carthage/Build/
74 |
75 | # Accio dependency management
76 | Dependencies/
77 | .accio/
78 |
79 | # fastlane
80 | # It is recommended to not store the screenshots in the git repo.
81 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
82 | # For more information about the recommended setup visit:
83 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
84 |
85 | fastlane/report.xml
86 | fastlane/Preview.html
87 | fastlane/screenshots/**/*.png
88 | fastlane/test_output
89 |
90 | # Code Injection
91 | # After new code Injection tools there's a generated folder /iOSInjectionProject
92 | # https://github.com/johnno1962/injectionforxcode
93 |
94 | iOSInjectionProject/
95 |
96 | ### Xcode ###
97 | # Xcode
98 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
99 |
100 |
101 | ## Gcc Patch
102 | /*.gcno
103 |
104 | ### Xcode Patch ###
105 | *.xcodeproj/*
106 | !*.xcodeproj/project.pbxproj
107 | !*.xcodeproj/xcshareddata/
108 | !*.xcworkspace/contents.xcworkspacedata
109 | **/xcshareddata/WorkspaceSettings.xcsettings
110 | AddaMeIOS/Views/Navigation_WithList.swift
111 | AddaMeIOS/Views/VarticalExample.swift
112 | AddaMeIOS/Configs/Production.xcconfig
113 | vendor
114 | .bundle/config
115 | AddaMeIOS/ExprimentViews
116 |
--------------------------------------------------------------------------------
/AddaMeIOS.xcodeproj/xcshareddata/xcschemes/AddaMeIOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 |
57 |
59 |
65 |
66 |
67 |
68 |
72 |
73 |
74 |
75 |
81 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/AddaMeIOS.xcodeproj/xcshareddata/xcschemes/AddaMeIOSUITests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/ChatAPI/ConversationAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConversationAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 14.10.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 | import Combine
11 |
12 | struct AddUser: Codable {
13 | let conversationsId: String
14 | let usersId: String
15 | }
16 |
17 | enum ConversationAPI {
18 | case list(_ query: QueryItem)
19 | case addUserToConversation(_ addUser: AddUser)
20 | case create(_ createConversation: CreateConversation)
21 | case find(conversationsId: String)
22 | }
23 |
24 | extension ConversationAPI: APIConfiguration {
25 |
26 | var pathPrefix: String {
27 | return "/conversations"
28 | }
29 |
30 | var path: String {
31 | return pathPrefix + {
32 | switch self {
33 | case .create: return String.empty
34 | case .addUserToConversation(let addUser):
35 | return "/\(addUser.conversationsId)/users/\(addUser.usersId)"
36 | case .list: return String.empty
37 | case .find(let conversationsId): return "/\(conversationsId)"
38 | }
39 | }()
40 | }
41 |
42 | var baseURL: URL { EnvironmentKeys.rootURL } // URL(string: "http://10.0.1.3:6060/v1")!
43 |
44 | var method: HTTPMethod {
45 | switch self {
46 | case .create, .addUserToConversation: return .post
47 | case .list, .find: return .get
48 | }
49 | }
50 |
51 | var dataType: DataType {
52 | switch self {
53 | case .create(let createConversation):
54 | return .requestWithEncodable(encodable: AnyEncodable(createConversation))
55 | case .addUserToConversation(let addUser):
56 | return .requestWithEncodable(encodable: AnyEncodable(addUser))
57 | case .list(let query):
58 | return .requestParameters(parameters: [
59 | query.page: query.pageNumber,
60 | query.per: query.perSize
61 | ])
62 | case .find: return .requestPlain
63 | }
64 | }
65 |
66 | var authType: AuthType {
67 | return .bearer(token:
68 | Authenticator.shared.currentToken?.accessToken ?? String.empty
69 | )
70 | }
71 |
72 | var contentType: ContentType? {
73 | switch self {
74 | case .create, .addUserToConversation, .list, .find:
75 | return .applicationJson
76 | }
77 | }
78 |
79 | var headers: [String : String]? {
80 | return nil
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/ChatAPI/MessageAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessageAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 02.10.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 | import Combine
11 |
12 | enum MessageAPI {
13 | case list(_ query: QueryItem, _ conversationId: String)
14 | }
15 |
16 | extension MessageAPI: APIConfiguration {
17 |
18 | var pathPrefix: String {
19 | return "/messages"
20 | }
21 |
22 | var path: String {
23 | return pathPrefix + {
24 | switch self {
25 | case .list(_, let conversationsId):
26 | return "/by/conversations/\(conversationsId)"
27 | }
28 | }()
29 | }
30 |
31 | var baseURL: URL { EnvironmentKeys.rootURL }
32 |
33 |
34 | var method: HTTPMethod {
35 | switch self {
36 | case .list: return .get
37 | }
38 | }
39 |
40 | var dataType: DataType {
41 | switch self {
42 | case .list(let query, _):
43 | return .requestParameters(parameters: [
44 | query.page: query.pageNumber,
45 | query.per: query.perSize
46 | ])
47 | }
48 | }
49 |
50 | var authType: AuthType {
51 | return .bearer(token:
52 | Authenticator.shared.currentToken?.accessToken ?? String.empty
53 | )
54 | }
55 |
56 | var contentType: ContentType? {
57 | switch self {
58 | case .list:
59 | return .applicationJson
60 | }
61 | }
62 |
63 | var headers: [String : String]? {
64 | return nil
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/ContactAPI/ContactAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 13.11.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 | import Combine
11 |
12 | enum ContactAPI {
13 | case create(contacts: [Contact])
14 | }
15 |
16 | extension ContactAPI: APIConfiguration {
17 | var baseURL: URL { EnvironmentKeys.rootURL }// { URL(string: "http://10.0.1.3:3030/v1")! } //{ EnvironmentKeys.rootURL }
18 |
19 | var pathPrefix: String {
20 | return "/contacts"
21 | }
22 |
23 | var path: String {
24 | return pathPrefix + {
25 | switch self {
26 | case .create: return String.empty
27 | }
28 | }()
29 | }
30 |
31 |
32 | var method: HTTPMethod {
33 | switch self {
34 | case .create: return .post
35 | }
36 | }
37 |
38 | var dataType: DataType {
39 | switch self {
40 | case .create(let contacts):
41 | return .requestWithEncodable(encodable: AnyEncodable(contacts))
42 | }
43 | }
44 |
45 | var authType: AuthType {
46 | return .bearer(token:
47 | Authenticator.shared.currentToken?.accessToken ?? String.empty
48 | )
49 | }
50 |
51 | var contentType: ContentType? {
52 | switch self {
53 | case .create:
54 | return .applicationJson
55 | }
56 | }
57 |
58 | var headers: [String : String]? {
59 | return nil
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/EventAPI/EventAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 28.08.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 | import Combine
11 |
12 | enum EventAPI {
13 | case events(_ query: EventQueryItem)
14 | case create(_ event: Event)
15 | case myEvents(_ query: QueryItem )
16 | }
17 |
18 | //extension RequiresAuth {
19 | //// var header: [String: String] { get }
20 | //// return .bearer(token:
21 | //// Authenticator.shared.currentToken?.accessToken ?? String.empty
22 | //// )
23 | // var header: [String: String] {
24 | // ["Authorization": "Bearer \(Authenticator.shared.currentToken?.accessToken ?? String.empty)"]
25 | // }
26 | //}
27 |
28 | ///planets?page=2&per=5
29 | struct QueryItem: Codable {
30 | var page: String
31 | var pageNumber: String
32 | var per: String
33 | var perSize: String
34 | }
35 |
36 | struct EventQueryItem: Codable {
37 | var page: String
38 | var pageNumber: String
39 | var per: String
40 | var perSize: String
41 | var lat: String
42 | var long: String
43 | var distance: String
44 | var latValue: String
45 | var longValue: String
46 | var distanceValue: String
47 |
48 | }
49 |
50 | extension EventAPI: APIConfiguration {
51 |
52 | var baseURL: URL { EnvironmentKeys.rootURL }
53 |
54 | var pathPrefix: String {
55 | return "/events"
56 | }
57 |
58 | var path: String {
59 | return pathPrefix + {
60 | switch self {
61 | case .create: return String.empty
62 | case .events: return String.empty
63 | case .myEvents: return "/my"
64 | }
65 | }()
66 | }
67 |
68 |
69 | var method: HTTPMethod {
70 | switch self {
71 | case .create: return .post
72 | case .events, .myEvents: return .get
73 | }
74 | }
75 |
76 | var dataType: DataType {
77 | switch self {
78 | case .create(let event):
79 | return .requestWithEncodable(encodable: AnyEncodable(event))
80 | case .events(let eventQuery):
81 | return .requestParameters(parameters: [
82 | eventQuery.page: eventQuery.pageNumber,
83 | eventQuery.per: eventQuery.perSize,
84 | eventQuery.lat: eventQuery.latValue,
85 | eventQuery.long: eventQuery.longValue,
86 | eventQuery.distance: eventQuery.distanceValue,
87 | ])
88 | case .myEvents(let query):
89 | return .requestParameters(parameters: [
90 | query.page: query.pageNumber,
91 | query.per: query.perSize
92 | ])
93 | }
94 | }
95 |
96 | var authType: AuthType {
97 | return .bearer(token:
98 | Authenticator.shared.currentToken?.accessToken ?? String.empty
99 | )
100 | }
101 |
102 | var contentType: ContentType? {
103 | switch self {
104 | case .create, .events, .myEvents:
105 | return .applicationJson
106 | }
107 | }
108 |
109 | var headers: [String : String]? {
110 | return nil
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/EventAPI/EventPlaceAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeoLocationAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 20.09.2020.
6 | //
7 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/UserAPI/AttachmentAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttachmentAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 19.11.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 |
11 | enum AttachmentAPI {
12 | case create(_ attachment: Attachment)
13 | }
14 |
15 | extension AttachmentAPI: APIConfiguration {
16 | var pathPrefix: String {
17 | return "attachments"
18 | }
19 |
20 | var path: String {
21 | return pathPrefix + {
22 | switch self {
23 | case .create: return ""
24 | }
25 | }()
26 | }
27 |
28 | var baseURL: URL { EnvironmentKeys.rootURL }
29 |
30 | var method: HTTPMethod {
31 | switch self {
32 | case .create: return .post
33 | }
34 | }
35 |
36 | var dataType: DataType {
37 | switch self {
38 | case .create(let attachment):
39 | return .requestWithEncodable(encodable: AnyEncodable(attachment))
40 | }
41 | }
42 |
43 | var authType: AuthType {
44 | return .bearer(token:
45 | Authenticator.shared.currentToken?.accessToken ?? String.empty
46 | )
47 | }
48 |
49 | var contentType: ContentType? {
50 | switch self {
51 | case .create:
52 | return .applicationJson
53 | }
54 | }
55 |
56 | var headers: [String : String]? {
57 | return nil
58 | }
59 |
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/UserAPI/AuthAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UesrAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 20.08.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 |
11 | enum AuthAPI {
12 | case login(login: LoginAndVerificationResponse)
13 | case verification(verificationResponse: LoginAndVerificationResponse)
14 | }
15 |
16 | extension AuthAPI: APIConfiguration {
17 | var pathPrefix: String {
18 | return "auth/"
19 | }
20 |
21 | var path: String {
22 | return pathPrefix + {
23 | switch self {
24 | case .login: return "login"
25 | case .verification: return "verify_sms"
26 | }
27 | }()
28 | }
29 |
30 | var baseURL: URL { EnvironmentKeys.rootURL }
31 |
32 | var method: HTTPMethod {
33 | switch self {
34 | case .login, .verification: return .post
35 | }
36 | }
37 |
38 | var dataType: DataType {
39 | switch self {
40 | case .login(let loginAndVerificationResponse):
41 | return .requestWithEncodable(encodable: AnyEncodable(loginAndVerificationResponse))
42 | case .verification(let verificationResponse):
43 | return .requestWithEncodable(encodable: AnyEncodable(verificationResponse))
44 | }
45 | }
46 |
47 | var authType: AuthType {
48 | return .none
49 | }
50 |
51 | var contentType: ContentType? {
52 | switch self {
53 | case .login, .verification:
54 | return .applicationJson
55 | }
56 | }
57 |
58 | var headers: [String : String]? {
59 | return nil
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/UserAPI/DeviceAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeviceAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 29.11.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 | import Combine
11 |
12 | enum DeviceAPI {
13 | case createOrUpdate(_ device: Device)
14 | }
15 |
16 | extension DeviceAPI: APIConfiguration {
17 | var pathPrefix: String {
18 | return "/devices"
19 | }
20 |
21 | var path: String {
22 | return pathPrefix + {
23 | switch self {
24 | case .createOrUpdate:
25 | return ""
26 | }
27 | }()
28 | }
29 |
30 | var baseURL: URL { EnvironmentKeys.rootURL }
31 |
32 |
33 | var method: HTTPMethod {
34 | switch self {
35 | case .createOrUpdate: return .post
36 | }
37 | }
38 |
39 | var dataType: DataType {
40 | switch self {
41 | case .createOrUpdate(let device):
42 | return .requestWithEncodable(encodable: AnyEncodable(device))
43 | }
44 | }
45 |
46 | var authType: AuthType {
47 | return .bearer(token:
48 | Authenticator.shared.currentToken?.accessToken ?? String.empty
49 | )
50 | }
51 |
52 | var contentType: ContentType? {
53 | switch self {
54 | case .createOrUpdate:
55 | return .applicationJson
56 | }
57 | }
58 |
59 | var headers: [String : String]? {
60 | return nil
61 | }
62 | }
63 |
64 | struct Device: Codable {
65 | var id: String?
66 | var ownerId: String
67 | var name: String
68 | var model: String?
69 | var osVersion: String?
70 | var token: String
71 | var voipToken: String
72 | var createAt, updatedAt: String?
73 | }
74 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/UserAPI/RefreshTokenAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RefreshTokenAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 28.08.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 |
11 | struct RefreshTokenResponse: Codable {
12 | var accessToken: String
13 | var refreshToken: String
14 | }
15 |
16 | struct RefreshTokenInput: Codable {
17 | var refreshToken: String
18 | }
19 |
20 | enum RefreshTokenAPI {
21 | case refresh(token: RefreshTokenInput)
22 | }
23 |
24 | extension RefreshTokenAPI: APIConfiguration {
25 | var path: String {
26 | return pathPrefix + {
27 | switch self {
28 | case .refresh:
29 | return "/refreshToken"
30 | }
31 | }()
32 | }
33 |
34 | var baseURL: URL { EnvironmentKeys.rootURL }
35 |
36 | var method: HTTPMethod {
37 | switch self {
38 | case .refresh: return .post
39 | }
40 | }
41 |
42 | var dataType: DataType {
43 | switch self {
44 | case .refresh(let rToken):
45 | return .requestWithEncodable(encodable: AnyEncodable(rToken))
46 | }
47 | }
48 |
49 | var authType: AuthType {
50 | return .bearer(token:
51 | Authenticator.shared.currentToken?.accessToken ?? String.empty
52 | )
53 | }
54 |
55 | var pathPrefix: String {
56 | return "auth/"
57 | }
58 |
59 | var contentType: ContentType? {
60 | switch self {
61 | case .refresh:
62 | return .applicationJson
63 | }
64 | }
65 |
66 | var headers: [String : String]? {
67 | return nil
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/AddaMeIOS/API/UserAPI/UserAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserAPI.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 17.10.2020.
6 | //
7 |
8 | import Foundation
9 | import Pyramid
10 |
11 | enum UserAPI {
12 | case me(_ usersId: String)
13 | case update(_ user: CurrentUser)
14 | }
15 |
16 | extension UserAPI: APIConfiguration {
17 | var pathPrefix: String {
18 | return "users"
19 | }
20 |
21 | var path: String {
22 | return pathPrefix + {
23 | switch self {
24 | case .me(let usersId): return "/\(usersId)"
25 | case .update: return ""
26 | }
27 | }()
28 | }
29 |
30 | var baseURL: URL { EnvironmentKeys.rootURL }
31 |
32 | var method: HTTPMethod {
33 | switch self {
34 | case .me: return .get
35 | case .update: return .put
36 | }
37 | }
38 |
39 | var dataType: DataType {
40 | switch self {
41 | case .me:
42 | return .requestPlain
43 | case .update(let user):
44 | return .requestWithEncodable(encodable: AnyEncodable(user))
45 | }
46 | }
47 |
48 | var authType: AuthType {
49 | return .bearer(token:
50 | Authenticator.shared.currentToken?.accessToken ?? String.empty
51 | )
52 | }
53 |
54 | var contentType: ContentType? {
55 | switch self {
56 | case .me, .update:
57 | return .applicationJson
58 | }
59 | }
60 |
61 | var headers: [String : String]? {
62 | return nil
63 | }
64 |
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/AddaMeIOS/AddaMeIOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 |
8 |
9 |
--------------------------------------------------------------------------------
/AddaMeIOS/App/AppDelegate+PKPushRegistryDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate+PKPushRegistryDelegate.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 29.11.2020.
6 | //
7 |
8 | import PushKit
9 | import UIKit
10 | import CallKit
11 |
12 | // MARK: PKPushRegistryDelegate
13 | extension AppDelegate: PKPushRegistryDelegate {
14 |
15 | func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
16 |
17 | print(#line, "voip token saved")
18 | let voipToken = credentials.token.toHexString()
19 | KeychainService.save(string: voipToken, for: .voipToken)
20 | }
21 |
22 | func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
23 | print(#line, "invalid \(self)")
24 | }
25 |
26 | func pushRegistry(
27 | _ registry: PKPushRegistry,
28 | didReceiveIncomingPushWith payload: PKPushPayload,
29 | for type: PKPushType, completion: @escaping () -> Void) {
30 |
31 |
32 | if type == .voIP {
33 | print(#line, "lets fire PKPushPayload voIP")
34 | if let handle = payload.dictionaryPayload["aps"] {
35 | print(#line, handle)
36 | }
37 | }
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/AddaMeIOS/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Alif on 11/7/20.
6 | //
7 |
8 | import UIKit
9 | import UserNotifications
10 | import PushKit
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | let voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 |
20 | registerForPushNotifications()
21 |
22 | return true
23 | }
24 |
25 | // MARK: UISceneSession Lifecycle
26 |
27 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
28 | // Called when a new scene session is being created.
29 | // Use this method to select a configuration to create the new scene with.
30 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
31 | }
32 |
33 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
34 | // Called when the user discards a scene session.
35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
37 | }
38 |
39 | func application(
40 | _ application: UIApplication,
41 | didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
42 | ) {
43 | let token = deviceToken.toHexString()
44 | KeychainService.save(string: token, for: .deviceToken)
45 | print("Device Token: \(token)")
46 | }
47 |
48 | func application(
49 | _ application: UIApplication,
50 | didFailToRegisterForRemoteNotificationsWithError error: Error
51 | ) {
52 | print("Failed to register: \(error)")
53 | }
54 |
55 | func registerForPushNotifications() {
56 |
57 | UNUserNotificationCenter.current()
58 | .requestAuthorization(
59 | options: [.alert, .sound, .badge]) { [weak self] granted, _ in
60 | print(#line, "Permission granted: \(granted)")
61 | guard granted else { return }
62 | self?.getNotificationSettings()
63 | }
64 |
65 | registerForPushNVoip()
66 | }
67 |
68 | func getNotificationSettings() {
69 | UNUserNotificationCenter.current().getNotificationSettings { settings in
70 | print(#line, "Notification settings: \(settings)")
71 | guard settings.authorizationStatus == .authorized else { return }
72 | DispatchQueue.main.async {
73 | UIApplication.shared.registerForRemoteNotifications()
74 | }
75 | }
76 | }
77 |
78 | private func registerForPushNVoip() {
79 | //register for voip notifications
80 | voipRegistry.delegate = self
81 | voipRegistry.desiredPushTypes = [.voIP]
82 | UIApplication.shared.registerForRemoteNotifications()
83 | }
84 |
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/AddaMeIOS/App/AppState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppState.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 06.11.2020.
6 | //
7 |
8 | import Combine
9 |
10 | class AppState: ObservableObject {
11 | @Published var currentTab = AppTabs.event
12 | @Published var tabBarIsHidden: Bool = false
13 | }
14 |
15 | enum AppTabs: Int {
16 | case event, chat, profile
17 | }
18 |
--------------------------------------------------------------------------------
/AddaMeIOS/App/AppTabView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabView.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 26.08.2020.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | struct AppTabView: View {
12 | @State var index = 0
13 | @State var expand = true
14 | @State var searchExpand = true
15 |
16 | @EnvironmentObject var appState: AppState
17 | // @EnvironmentObject var conversationView: ConversationViewModel
18 | @Environment(\.colorScheme) var colorScheme
19 |
20 | // var newConversationCountText: String {
21 | // if conversationView.conversations.isEmpty {
22 | // return "Chat"
23 | // } else {
24 | // return "Chat - \(conversationView.conversations.count)"
25 | // }
26 | // }
27 |
28 | @ViewBuilder var body: some View {
29 |
30 | VStack(alignment: .center) {
31 | ZStack {
32 | if index == 0 {
33 | EventList()
34 | .environmentObject(appState)
35 | } else if index == 1 {
36 | ConversationList()
37 | .environmentObject(appState)
38 | } else if index == 2 {
39 | ProfileView()
40 | .environmentObject(appState)
41 | }
42 | }
43 |
44 | Spacer()
45 | if appState.tabBarIsHidden == false {
46 | CustomTabs(index: self.$index, expand: self.$expand)
47 | }
48 | }
49 | .background(Color(.systemBackground))
50 | .edgesIgnoringSafeArea(.all)
51 | }
52 | }
53 |
54 | struct AppTabView_Previews: PreviewProvider {
55 | static var previews: some View {
56 | AppTabView()
57 | // .environmentObject(ConversationViewModel())
58 | }
59 | }
60 |
61 | struct CustomTabs: View {
62 | @Binding var index: Int
63 | @Binding var expand: Bool
64 |
65 | @Environment(\.colorScheme) var colorScheme
66 |
67 | var body: some View {
68 | HStack {
69 |
70 | Button(action: {
71 | self.index = 0
72 | self.expand = true
73 | }) {
74 | VStack {
75 | Image(systemName: "captions.bubble")
76 | Text("Events")
77 | }
78 | }
79 | .foregroundColor(Color("bg").opacity(self.index == 0 ? 1 : 0.6))
80 | Spacer()
81 |
82 | Button(action: {
83 | self.expand = false
84 | self.index = 1
85 | }) {
86 | VStack {
87 | Image(systemName: "bubble.left.and.bubble.right.fill")
88 | Text("Chat")
89 | }
90 | }
91 | .foregroundColor(Color("bg").opacity(self.index == 1 ? 1 : 0.6))
92 |
93 | Spacer()
94 |
95 | Button(action: {
96 | self.expand = false
97 | self.index = 2
98 | }) {
99 | VStack {
100 | Image(systemName: "person.fill")
101 | Text("Profile")
102 | }
103 | }
104 | .foregroundColor(Color("bg").opacity(self.index == 2 ? 1 : 0.6))
105 |
106 | }
107 | .padding([.bottom, .trailing, .leading], 40)
108 | .background(Color(.systemBackground))
109 | }
110 | }
111 |
112 | struct CustomTabs_Previews: PreviewProvider {
113 | static var previews: some View {
114 | CustomTabs(index: .constant(1), expand: .constant(true))
115 | // .environmentObject(ConversationViewModel())
116 | }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/AddaMeIOS/App/Persistence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Persistence.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 01.12.2020.
6 | //
7 |
8 | import CoreData
9 |
10 | struct PersistenceController {
11 | static let shared = PersistenceController()
12 |
13 | public var moc: NSManagedObjectContext {
14 | return container.viewContext
15 | }
16 |
17 | static public var preview: PersistenceController = {
18 | let result = PersistenceController(inMemory: false)
19 | let viewContext = result.container.viewContext
20 |
21 | if viewContext.hasChanges {
22 | do {
23 | try viewContext.save()
24 | } catch {
25 | // The context couldn't be saved.
26 | // You should add your own error handling here.
27 | let nserror = error as NSError
28 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
29 | }
30 | }
31 |
32 | return result
33 |
34 | }()
35 |
36 | let container: NSPersistentContainer
37 |
38 | init(inMemory: Bool = false) {
39 | container = NSPersistentContainer(name: "AddaModel")
40 |
41 | if inMemory {
42 | container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
43 | }
44 |
45 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in
46 | if let error = error as NSError? {
47 | fatalError("Unresolved error \(error), \(error.userInfo)")
48 | }
49 | })
50 |
51 | container.viewContext.automaticallyMergesChangesFromParent = true
52 | container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
53 | }
54 |
55 | // MARK: - Core Data Saving support
56 | public func saveContext () {
57 | let context = container.viewContext
58 | if context.hasChanges {
59 | do {
60 | try context.save()
61 | } catch {
62 | let nserror = error as NSError
63 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
64 | }
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/AddaMeIOS/App/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Alif on 11/7/20.
6 | //
7 |
8 | import UIKit
9 | import SwiftUI
10 | import Combine
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 |
18 | let conentView = RootView()
19 | .modifier(SystemServices())
20 |
21 | if let windowScene = scene as? UIWindowScene {
22 | let window = UIWindow(windowScene: windowScene)
23 |
24 | window.rootViewController = UIHostingController(rootView: conentView)
25 | self.window = window
26 | window.makeKeyAndVisible()
27 | }
28 | }
29 |
30 | func sceneDidDisconnect(_ scene: UIScene) {
31 | // Called as the scene is being released by the system.
32 | // This occurs shortly after the scene enters the background, or when its session is discarded.
33 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
34 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
35 | }
36 |
37 | func sceneDidBecomeActive(_ scene: UIScene) {
38 | // Called when the scene has moved from an inactive state to an active state.
39 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
40 |
41 | guard let _ : CurrentUser = KeychainService.loadCodable(for: .currentUser) else {
42 | print(#line, "Missing current user from KeychainService")
43 | return
44 | }
45 |
46 | guard UserDefaults.standard.bool(forKey: "isAuthorized") == true else { return }
47 |
48 | _ = SocketViewModel.shared
49 | }
50 |
51 | func sceneWillResignActive(_ scene: UIScene) {
52 | // Called when the scene will move from an active state to an inactive state.
53 | // This may occur due to temporary interruptions (ex. an incoming phone call).
54 | }
55 |
56 | func sceneWillEnterForeground(_ scene: UIScene) {
57 | // Called as the scene transitions from the background to the foreground.
58 | // Use this method to undo the changes made on entering the background.
59 | }
60 |
61 | func sceneDidEnterBackground(_ scene: UIScene) {
62 | // Called as the scene transitions from the foreground to the background.
63 | // Use this method to save data, release shared resources, and store enough scene-specific state information
64 | // to restore the scene back to its current state.
65 | }
66 |
67 |
68 | }
69 |
70 | extension SceneDelegate: UIGestureRecognizerDelegate {
71 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
72 | return true
73 | }
74 | }
75 |
76 | class AnyGestureRecognizer: UIGestureRecognizer {
77 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
78 | if let touchedView = touches.first?.view, touchedView is UIControl {
79 | state = .cancelled
80 |
81 | } else if let touchedView = touches.first?.view as? UITextView, touchedView.isEditable {
82 | state = .cancelled
83 |
84 | } else {
85 | state = .began
86 | }
87 | }
88 |
89 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
90 | state = .ended
91 | }
92 |
93 | override func touchesCancelled(_ touches: Set, with event: UIEvent) {
94 | state = .cancelled
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-App-20x20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "Icon-App-20x20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "Icon-App-29x29@1x.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "Icon-App-29x29@2x.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "Icon-App-29x29@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "Icon-App-40x40@2x.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "Icon-App-40x40@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "Icon-App-60x60@2x.png",
47 | "idiom" : "iphone",
48 | "scale" : "2x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "Icon-App-60x60@3x.png",
53 | "idiom" : "iphone",
54 | "scale" : "3x",
55 | "size" : "60x60"
56 | },
57 | {
58 | "filename" : "Icon-App-20x20@1x.png",
59 | "idiom" : "ipad",
60 | "scale" : "1x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "Icon-App-20x20@2x.png",
65 | "idiom" : "ipad",
66 | "scale" : "2x",
67 | "size" : "20x20"
68 | },
69 | {
70 | "filename" : "Icon-App-29x29@1x.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "Icon-App-29x29@2x.png",
77 | "idiom" : "ipad",
78 | "scale" : "2x",
79 | "size" : "29x29"
80 | },
81 | {
82 | "filename" : "Icon-App-40x40@1x.png",
83 | "idiom" : "ipad",
84 | "scale" : "1x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "Icon-App-40x40@2x.png",
89 | "idiom" : "ipad",
90 | "scale" : "2x",
91 | "size" : "40x40"
92 | },
93 | {
94 | "filename" : "Icon-App-76x76@1x.png",
95 | "idiom" : "ipad",
96 | "scale" : "1x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "Icon-App-76x76@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "76x76"
104 | },
105 | {
106 | "filename" : "Icon-App-83.5x83.5@2x.png",
107 | "idiom" : "ipad",
108 | "scale" : "2x",
109 | "size" : "83.5x83.5"
110 | },
111 | {
112 | "filename" : "ItunesArtwork@2x.png",
113 | "idiom" : "ios-marketing",
114 | "scale" : "1x",
115 | "size" : "1024x1024"
116 | }
117 | ],
118 | "info" : {
119 | "author" : "xcode",
120 | "version" : 1
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/Avatar.imageset/Avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/Avatar.imageset/Avatar.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/Avatar.imageset/Avatar@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/Avatar.imageset/Avatar@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/Avatar.imageset/Avatar@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/Avatar.imageset/Avatar@3x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/Avatar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Avatar.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Avatar@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Avatar@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/Color.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.938",
9 | "green" : "0.938",
10 | "red" : "0.938"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/bg.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF9",
9 | "green" : "0x79",
10 | "red" : "0x5E"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/iTunesArtwork.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "iTunesArtwork@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "iTunesArtwork@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "iTunesArtwork@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/iTunesArtwork.imageset/iTunesArtwork@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/iTunesArtwork.imageset/iTunesArtwork@1x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/iTunesArtwork.imageset/iTunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/iTunesArtwork.imageset/iTunesArtwork@2x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Assets.xcassets/iTunesArtwork.imageset/iTunesArtwork@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOSSwiftUI/ddb62dba6d4f2c0073f939646037c260faf9675a/AddaMeIOS/Assets.xcassets/iTunesArtwork.imageset/iTunesArtwork@3x.png
--------------------------------------------------------------------------------
/AddaMeIOS/Auth/AuthViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthViewModel.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Alif on 4/8/20.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 | import Pyramid
11 | import SwiftUI
12 |
13 | enum AddaError: Error {
14 | case guardFail(String)
15 | }
16 |
17 | extension AddaError {
18 | var reason: String {
19 | switch self {
20 | case .guardFail(let location):
21 | return "guard statment fail \(location)"
22 | }
23 | }
24 | }
25 |
26 | class AuthViewModel: ObservableObject {
27 |
28 | @AppStorage(AppUserDefaults.Key.isAuthorized.rawValue) var isAuthorized: Bool = false
29 | @AppStorage(AppUserDefaults.Key.isUserFristNameUpdated.rawValue) var isUserFristNameUpdated: Bool = false
30 |
31 | @Published var lAndVRes = LoginAndVerificationResponse(phoneNumber: String.empty)
32 | @Published var verificationCodeResponse = String.empty {
33 | didSet {
34 | lAndVRes.code = verificationCodeResponse
35 | if verificationCodeResponse.count == 6 {
36 | verification()
37 | }
38 | }
39 | }
40 |
41 | @Published var isLoadingPage = false
42 | @Published var inputWrongVarificationCode = false
43 |
44 | let provider = Pyramid()
45 | var cancellationToken: AnyCancellable?
46 |
47 | var anyCancellable: AnyCancellable? = nil
48 |
49 | init() {}
50 |
51 | }
52 |
53 | extension AuthViewModel {
54 | func login() {
55 |
56 | isLoadingPage = true
57 |
58 | guard !lAndVRes.phoneNumber.isEmpty else {
59 | return
60 | }
61 |
62 | cancellationToken = provider.request(
63 | with: AuthAPI.login(login: lAndVRes),
64 | scheduler: RunLoop.main,
65 | class: LoginAndVerificationResponse.self
66 | ).sink(receiveCompletion: { [weak self] completionResponse in
67 | switch completionResponse {
68 | case .failure(let error):
69 | self?.isLoadingPage = false
70 | print(#line, error.errorDescription)
71 | case .finished:
72 | self?.isLoadingPage = false
73 | break
74 | }
75 | }, receiveValue: { [weak self] res in
76 | print(res)
77 | self?.isLoadingPage = false
78 | self?.lAndVRes = res
79 | })
80 | }
81 |
82 | func verification() {
83 | isLoadingPage = true
84 |
85 | guard !lAndVRes.phoneNumber.isEmpty else {
86 | return
87 | }
88 |
89 | cancellationToken = provider.request(
90 | with: AuthAPI.verification(verificationResponse: lAndVRes),
91 | scheduler: RunLoop.main,
92 | class: LoginRes.self
93 | ).sink(receiveCompletion: { [weak self] completionResponse in
94 | switch completionResponse {
95 | case .failure(let error):
96 | print(#line, error.errorDescription)
97 | self?.inputWrongVarificationCode = true
98 | self?.isLoadingPage = false
99 | case .finished:
100 | self?.isLoadingPage = false
101 | break
102 | }
103 | }, receiveValue: { [weak self] res in
104 | print(res)
105 | print("Your have login")
106 | guard let self = self else { return }
107 |
108 | AppUserDefaults.saveCurrentUserAndToken(res)
109 | self.isAuthorized = true
110 | self.isLoadingPage = false
111 |
112 | }) // add more logic loading
113 | }
114 | }
115 |
116 |
--------------------------------------------------------------------------------
/AddaMeIOS/Auth/HUDProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HUDProgressView.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 27.10.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct HUDProgressView: View {
11 | var placeHolder: String
12 | @Binding var show: Bool
13 | @State var animate = false
14 |
15 | var body: some View {
16 | VStack {
17 | Circle()
18 | .stroke(AngularGradient(gradient: .init(colors: [Color(.systemBlue), Color.primary.opacity(0)]), center: .center))
19 | .frame(width: 80, height: 80)
20 | .rotationEffect(.init(degrees: animate ? 360 : 0))
21 |
22 | Text(placeHolder)
23 | .fontWeight(.bold)
24 | .foregroundColor(.white)
25 |
26 | }
27 | .padding(.vertical, 25)
28 | .padding(.horizontal, 35)
29 | // .background(BlueView())
30 | .cornerRadius(20)
31 | .frame(maxWidth: .infinity, maxHeight: .infinity)
32 | .background(
33 | Color.clear
34 | .onTapGesture {
35 | withAnimation {
36 | show.toggle()
37 | }
38 | }
39 | )
40 | .onAppear {
41 | withAnimation(
42 | Animation.linear(duration: 1.5)
43 | .repeatForever(autoreverses: false)) {
44 | animate.toggle()
45 | }
46 | }
47 | }
48 | }
49 |
50 | struct BlurView: UIViewRepresentable {
51 | func makeUIView(context: Context) -> some UIView {
52 | let effect = UIBlurEffect(style: .extraLight)
53 | let view = UIVisualEffectView(effect: effect)
54 | return view
55 | }
56 |
57 | func updateUIView(_ uiView: UIViewType, context: Context) {
58 |
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/AddaMeIOS/Auth/LoginAndVerificationResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginAndVerificationResponse.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 24.08.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - Login and Verification request/response
11 | struct LoginAndVerificationResponse: Codable {
12 | internal init(
13 | phoneNumber: String,
14 | attemptId: String? = nil,
15 | code: String? = nil,
16 | isLoggedIn: Bool = false
17 | ) {
18 | self.phoneNumber = phoneNumber
19 | self.attemptId = attemptId
20 | self.code = code
21 | self.isLoggedIn = isLoggedIn
22 | }
23 |
24 | var phoneNumber: String
25 | var attemptId: String?
26 | var code: String?
27 | var isLoggedIn: Bool? = false
28 | }
29 |
30 | // MARK: - Login Response
31 | struct LoginRes: Codable {
32 | let status: String
33 | let user: CurrentUser
34 | let access: Access
35 | }
36 |
37 | // MARK: - Access
38 | struct Access: Codable {
39 | let accessToken, refreshToken: String
40 | }
41 |
42 |
43 |
--------------------------------------------------------------------------------
/AddaMeIOS/Auth/RootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootView.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 06.11.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RootView: View {
11 |
12 | @AppStorage(AppUserDefaults.Key.isUserFristNameUpdated.rawValue) var isUserFristNameUpdated: Bool = false
13 |
14 | @ViewBuilder var body: some View {
15 | if isUserFristNameUpdated {
16 | AppTabView()
17 | } else {
18 | AuthView()
19 | }
20 | }
21 |
22 | }
23 |
24 | struct RootView_Previews: PreviewProvider {
25 | static var previews: some View {
26 | RootView()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/AddaMeIOS/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/AddaMeIOS/Chat/Authenticator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Authenticator.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 26.08.2020.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 | import Pyramid
11 | import SwiftUI
12 |
13 | class Authenticator: ObservableObject {
14 | static let shared = Authenticator()
15 | let provider = Pyramid()
16 | var cancellables = Set()
17 | @AppStorage(AppUserDefaults.Key.isAuthorized.rawValue) var isAuthorized: Bool = false
18 |
19 |
20 | var currentToken: Access? {
21 | get {
22 | guard
23 | let token: Access = KeychainService.loadCodable(for: .token),
24 | isAuthorized == true else {
25 | print(#line, "not Authorized Token are missing")
26 | return nil
27 | }
28 |
29 | return token
30 |
31 | } set {
32 | KeychainService.save(codable: newValue, for: .token)
33 | }
34 | }
35 |
36 | init() {}
37 |
38 | private var cancellation: AnyCancellable?
39 |
40 | func refreshToken(using subject: S) where S.Output == RefreshTokenResponse {
41 | //self.currentToken = Token(isValid: true)
42 | //let referehTokenInput = RefreshTokenInput(refreshToken: currentToken!.refreshToken)
43 | // provider.request(
44 | // with: RefreshTokenAPI.refresh(token: referehTokenInput),
45 | // scheduler: RunLoop.main,
46 | // class: RefreshTokenResponse.self
47 | // )
48 | // .retry(3)
49 | // .sink(receiveCompletion: { completionResponse in
50 | // switch completionResponse {
51 | // case .failure(let error):
52 | // print(#line, error)
53 | //// print(#line, error.errorDescription.contains("401"))
54 | // case .finished:
55 | // break
56 | // }
57 | // }, receiveValue: { res in
58 | // print(res)
59 | // KeychainService.save(codable: res, for: .token)
60 | // subject.send(self.currentToken!)
61 | // }).store(in: &cancellables)
62 |
63 | // let headers = [
64 | // "authorization": "Bearer \(currentToken?.accessToken)",
65 | // "Content-Type": "application/json"
66 | // ]
67 | //
68 | // let parameters: [String: Any] = [
69 | // "refresh_token": currentToken?.refreshToken,
70 | // ]
71 | //
72 | // let jsonDecoder: JSONDecoder = .ISO8601JSONDecoder
73 | // let url: URL = URL(string: "http://10.0.1.3:8080/v1/auth/refreshToken")!
74 | // var request = URLRequest(url: url)
75 | // request.httpMethod = "POST"
76 | // request.allHTTPHeaderFields = headers
77 | //
78 | // do {
79 | // request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
80 | // } catch let error {
81 | // print(error.localizedDescription)
82 | // }
83 | //
84 | // URLSession.shared.dataTaskPublisher(for: request)
85 | // .sink { com in
86 | // print(com)
87 | // } receiveValue: { data in
88 | // print(#line, data)
89 | //
90 | // do {
91 | // let rtResponse = try JSONDecoder().decode(RefreshTokenResponse.self, from: data.data)
92 | //
93 | //
94 | // KeychainService.save(codable: rtResponse, for: .token)
95 | //
96 | // subject.send(self.currentToken!)
97 | // } catch {
98 | // print(#line, error)
99 | // }
100 | //
101 | // }.store(in: &cancellables)
102 | }
103 |
104 | func tokenSubject() -> CurrentValueSubject {
105 | return CurrentValueSubject(currentToken!)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/AddaMeIOS/Chat/ChatBottomView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatBottomView.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 05.09.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChatBottomView: View {
11 |
12 | @State var composedMessage: String = String.empty
13 | @State var isMicButtonHide = false
14 | @State var preWordCount: Int = 0
15 | @State var newLineCount = 1
16 | @State var placeholderString: String = "Type..."
17 | @State var tEheight: CGFloat = 40
18 |
19 | @EnvironmentObject var chatData: ChatDataHandler
20 |
21 | private func onComment() {
22 | chatData.send()
23 | chatData.clearComposedMessage()
24 | tEheight = 40
25 | }
26 |
27 | var body: some View {
28 | ZStack {
29 | VStack {
30 | // Spacer()
31 | HStack {
32 | TextEditor(text: self.$chatData.composedMessage)
33 | .foregroundColor(self.chatData.composedMessage == placeholderString ? .gray : .primary)
34 | .onChange(of: self.chatData.composedMessage, perform: { value in
35 | preWordCount = value.split { $0.isNewline }.count
36 | if preWordCount == newLineCount && preWordCount < 9 {
37 | newLineCount += 1
38 | tEheight += 20
39 | }
40 |
41 | if chatData.composedMessage == String.empty {
42 | tEheight = 40
43 | }
44 | })
45 | .lineLimit(9) // its does not work ios 14 and swiftui 2.0
46 | .font(Font.system(size: 20, weight: .thin, design: .rounded))
47 | .frame(height: tEheight)
48 | .onTapGesture {
49 | if self.chatData.composedMessage == placeholderString {
50 | self.chatData.composedMessage = String.empty
51 | }
52 | }
53 | .padding([.trailing, .leading], 10)
54 | .background(RoundedRectangle(cornerRadius: 8).stroke())
55 | .background(Color.clear)
56 |
57 | Button(action: onComment) {
58 | Image(systemName: "arrow.up")
59 | //.resizable()
60 | .imageScale(.large)
61 | .frame(width: 23, height: 23)
62 | .padding(11)
63 | .foregroundColor(.white)
64 | .background(self.chatData.newMessageTextIsEmpty ? Color.gray : Color("bg"))
65 | .clipShape(Circle())
66 | }
67 | .disabled(self.chatData.newMessageTextIsEmpty)
68 | .foregroundColor(.gray)
69 |
70 | }
71 | .frame(height: 55)
72 | .padding(.horizontal, 15)
73 | .background(Color.clear)
74 | .modifier(AdaptsToSoftwareKeyboard())
75 |
76 | }
77 | }
78 |
79 | }
80 |
81 | }
82 |
83 | struct ChatBottomView_Previews: PreviewProvider {
84 | static var previews: some View {
85 | ChatBottomView()
86 | .environmentObject(ChatDataHandler())
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/AddaMeIOS/Chat/ChatOutGoingEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatOutGoingEvent.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 29.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | private let jsonEncoder: JSONEncoder = {
11 | let encoder = JSONEncoder()
12 | encoder.dateEncodingStrategy = .iso8601
13 | return encoder
14 | }()
15 |
16 | enum ChatOutGoingEvent: Encodable, Decodable {
17 |
18 | case conversation(ChatMessageResponse.Item)
19 | case message(ChatMessageResponse.Item)
20 | case connect(CurrentUser)
21 | case disconnect(CurrentUser)
22 | case notice(String)
23 | case error(String)
24 |
25 | private enum CodingKeys: String, CodingKey {
26 | case type, user, message, conversation
27 | }
28 |
29 | enum CodingError: Error {
30 | case unknownValue
31 | }
32 |
33 | init(from decoder: Decoder) throws {
34 | let container = try decoder.container(keyedBy: CodingKeys.self)
35 | let type = try container.decode(String.self, forKey: .type)
36 |
37 | switch type {
38 | case "connect":
39 | let connect = try container.decode(CurrentUser.self, forKey: .user)
40 | self = .connect(connect)
41 | case "disconnect":
42 | let disconnect = try container.decode(CurrentUser.self, forKey: .user)
43 | self = .disconnect(disconnect)
44 | case "message":
45 | let message = try container.decode(ChatMessageResponse.Item.self, forKey: .message)
46 | self = .message(message)
47 | case "conversation":
48 | let lastMessage = try container.decode(ChatMessageResponse.Item.self, forKey: .conversation)
49 | self = .conversation(lastMessage)
50 | case "notice":
51 | let notice = try container.decode(String.self, forKey: .message)
52 | self = .notice(notice)
53 | case "error":
54 | let error = try container.decode(String.self, forKey: .message)
55 | self = .error(error)
56 | default:
57 | throw CodingError.unknownValue
58 | }
59 | }
60 |
61 | func encode(to encoder: Encoder) throws {
62 | var kvc = encoder.container(keyedBy: String.self)
63 |
64 | switch self {
65 | case .connect(let user):
66 | try kvc.encode("connect", forKey: "type")
67 | try kvc.encode(user, forKey: "user")
68 | case .disconnect(let user):
69 | try kvc.encode("disconnect", forKey: "type")
70 | try kvc.encode(user, forKey: "user")
71 | case .message(let message):
72 | try kvc.encode("message", forKey: "type")
73 | try kvc.encode(message, forKey: "message")
74 | case .conversation(let conversation):
75 | try kvc.encode("conversation", forKey: "type")
76 | try kvc.encode(conversation, forKey: "conversation")
77 | case .notice(let message):
78 | try kvc.encode("notice", forKey: "type")
79 | try kvc.encode(message, forKey: "message")
80 | case .error(let error):
81 | try kvc.encode("error", forKey: "type")
82 | try kvc.encode(error, forKey: "message")
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/AddaMeIOS/Chat/ChatRoomView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatDetails.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 05.09.2020.
6 | //
7 |
8 | import Combine
9 | import SwiftUI
10 |
11 | struct ChatRoomView: View {
12 |
13 | @Environment(\.colorScheme) var colorScheme
14 | @Environment(\.presentationMode) var presentationMode
15 |
16 | @StateObject private var chatData = ChatDataHandler()
17 |
18 | @State var conversation: ConversationResponse.Item!
19 | @State var isMicButtonHide = false
20 |
21 | private func onApperAction() {
22 | self.chatData.conversationsId = conversation.id
23 | self.chatData.fetchMoreMessages()
24 | }
25 |
26 | @State var fromContactsOrEvents: Bool
27 |
28 | var body: some View {
29 | VStack {
30 | ZStack {
31 | List {
32 | // self.chatData.socket.messages[conversation.id] ?? [] // chatDemoData
33 | ForEach( self.chatData.socket.messages[conversation.id] ?? [] ) { message in
34 | LazyView(ChatRow(chatMessageResponse: message))
35 | .padding([.top, .bottom], 5)
36 | .padding([.leading, .trailing], -10)
37 | .onAppear {
38 | self.chatData.fetchMoreMessagIfNeeded(currentItem: message)
39 | }
40 | .scaleEffect(x: 1, y: -1, anchor: .center)
41 | .hideRowSeparator()
42 | .background(Color(.systemBackground))
43 | .foregroundColor(Color(.systemBackground))
44 | }
45 |
46 |
47 | if self.chatData.isLoadingPage {
48 | ProgressView()
49 | }
50 | }
51 | .scaleEffect(x: 1, y: -1, anchor: .center)
52 | .offset(x: 0, y: 2)
53 | .padding(10)
54 | .onAppear(perform: {
55 | onApperAction()
56 | })
57 | .navigationBarTitle("\(conversation.title)", displayMode: .inline)
58 | .background(Color(.systemBackground))
59 | .resignKeyboardOnDragGesture()
60 |
61 | }
62 | Spacer()
63 | ChatBottomView()
64 | .environmentObject(chatData)
65 | }
66 | .overlay(
67 | DismissButton(fromContactsOrEvents),
68 | alignment: .topLeading
69 | )
70 | .ignoresSafeArea(.keyboard, edges: .bottom)
71 | .animation(.default)
72 | }
73 |
74 | }
75 |
76 | struct ChatDetailsView_Previews: PreviewProvider {
77 | static var previews: some View {
78 | ChatRoomView(conversation: conversationData.last!, fromContactsOrEvents: true)
79 | .environmentObject(ChatDataHandler())
80 | }
81 | }
82 |
83 | struct DismissButton: View {
84 | @Environment(\.presentationMode) var presentationMode
85 |
86 | var fromContactsOrEvents: Bool
87 |
88 | init(_ fromContactsOrEvents: Bool) {
89 | self.fromContactsOrEvents = fromContactsOrEvents
90 | }
91 |
92 | var body: some View {
93 | HStack {
94 | if fromContactsOrEvents {
95 | Button(action: {
96 | presentationMode.wrappedValue.dismiss()
97 | }, label: {
98 | Image(systemName: "xmark.circle").font(.title)
99 | })
100 | .padding(.top, -10)
101 | .padding()
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/AddaMeIOS/Chat/ChatRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatRow.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 28.09.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChatRow : View {
11 | let image = ImageDraw()
12 | var chatMessageResponse: ChatMessageResponse.Item
13 |
14 | @Environment(\.colorScheme) var colorScheme
15 |
16 | @ViewBuilder var body: some View {
17 | Group {
18 |
19 | if !currenuser(chatMessageResponse.sender.id) {
20 | HStack {
21 | Group {
22 |
23 | if chatMessageResponse.sender.avatarUrl != nil {
24 | AsyncImage(
25 | urlString: chatMessageResponse.sender.avatarUrl,
26 | placeholder: { Text("Loading...").frame(width: 40, height: 40, alignment: .center) },
27 | image: {
28 | Image(uiImage: $0).resizable()
29 | }
30 | )
31 | .aspectRatio(contentMode: .fit)
32 | .frame(width: 40, height: 40)
33 | .clipShape(Circle())
34 | } else {
35 | Image(uiImage: image.random())
36 | .aspectRatio(contentMode: .fit)
37 | .frame(width: 40, height: 40)
38 | .clipShape(Circle())
39 | }
40 |
41 | Text(chatMessageResponse.messageBody)
42 | .bold()
43 | .padding(10)
44 | .foregroundColor(Color.white)
45 | .background(Color.blue)
46 | .cornerRadius(10)
47 | }
48 | .background(Color(.systemBackground))
49 | Spacer()
50 | }
51 | .background(Color(.systemBackground))
52 | } else {
53 | HStack {
54 | Group {
55 | Spacer()
56 | Text(chatMessageResponse.messageBody)
57 | .bold()
58 | .foregroundColor(Color.white)
59 | .padding(10)
60 | .background(Color.red)
61 | .cornerRadius(10)
62 |
63 | if chatMessageResponse.sender.avatarUrl != nil {
64 | AsyncImage(
65 | urlString: chatMessageResponse.sender.avatarUrl,
66 | placeholder: { Text("Loading...").frame(width: 40, height: 40, alignment: .center) },
67 | image: {
68 | Image(uiImage: $0).resizable()
69 | }
70 | )
71 | .aspectRatio(contentMode: .fit)
72 | .frame(width: 40, height: 40)
73 | .clipShape(Circle())
74 | } else {
75 | Image(uiImage: image.random())
76 | .aspectRatio(contentMode: .fit)
77 | .frame(width: 40, height: 40)
78 | .clipShape(Circle())
79 | }
80 |
81 | }
82 |
83 | }
84 | .background(Color(.systemBackground))
85 | }
86 | }
87 | .background(Color(.systemBackground))
88 | }
89 |
90 | func currenuser(_ userId: String) -> Bool {
91 | guard let currentUSER: CurrentUser = KeychainService.loadCodable(for: .currentUser) else {
92 | return false
93 | }
94 |
95 | return currentUSER.id == userId ? true : false
96 |
97 | }
98 | }
99 |
100 |
101 | struct ChatRow_Previews: PreviewProvider {
102 | static var previews: some View {
103 | ChatRow(chatMessageResponse: chatDemoData[1])
104 | .environmentObject(ChatDataHandler())
105 | .environment(\.colorScheme, .dark)
106 | }
107 | }
108 |
109 | // fixed this image issue i mean night mode issue
110 | //#imageLiteral(resourceName: "IMG_9505 3.PNG")
111 |
--------------------------------------------------------------------------------
/AddaMeIOS/Configs/Development.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Development.xcconfig
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 02.11.2020.
6 | //
7 |
8 | // Configuration settings file format documentation can be found at:
9 | // https://help.apple.com/xcode/#/dev745c5c974
10 |
11 | // Server URL
12 | ROOT_URL = http:/$()/10.0.1.3:8080/v1
13 | WEB_SOCKET_URL = ws:/$()/10.0.1.3:6060/v1/chat
14 |
15 | // Image Uploading to DigitalOcen
16 | ACCESS_KEY_ID = Z4R4QP36Q4JH3LZXU4BC
17 | SECRET_ACCESS_KEY = +RqibSk+I6D1/9Tdpm/afnpUuHaBP2lV4Rg+HKAYbRE
18 |
19 |
--------------------------------------------------------------------------------
/AddaMeIOS/Conversation/ConversationList.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessageListView.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 05.09.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ConversationList: View {
11 |
12 | @StateObject var contactStore: ContactStore = ContactStore()
13 | @StateObject var conversationViewModel = ConversationViewModel()
14 | @EnvironmentObject var appState: AppState
15 | @Environment(\.colorScheme) var colorScheme
16 |
17 | @State var distanationTag = false
18 | @State var moveToContacts = false
19 |
20 | @ViewBuilder var body : some View {
21 | NavigationView {
22 | List {
23 | ForEach(conversationViewModel.socket.conversations.map { $1 }.sorted(), id: \.self) { conversation in
24 | NavigationLink(
25 | destination: ChatRoomView(conversation: conversation, fromContactsOrEvents: false)
26 | .environmentObject(appState)
27 | .onAppear(perform: {
28 | appState.tabBarIsHidden = true
29 | })
30 | .onDisappear(perform: {
31 | appState.tabBarIsHidden = false
32 | })
33 | ) {
34 |
35 | ConversationRow(conversation: conversation)
36 | .environmentObject(appState)
37 | .onAppear {
38 | conversationViewModel.fetchMoreEventIfNeeded(
39 | currentItem: conversation
40 | )
41 | }
42 |
43 | }
44 | }
45 |
46 | if conversationViewModel.isLoadingPage {
47 | ProgressView()
48 | }
49 |
50 | }
51 | .onAppear {
52 | self.conversationViewModel.fetchMoreConversations()
53 | }
54 | .navigationTitle("Chats")
55 | .toolbar {
56 | ToolbarItem(placement: .navigationBarTrailing ) { contactList }
57 | }
58 | .background(Color(.systemBackground))
59 | }
60 | .navigationViewStyle(StackNavigationViewStyle())
61 | }
62 |
63 | private var contactList: some View {
64 | Button(action: {
65 | self.moveToContacts = true
66 | }) {
67 | Image(systemName: "plus.circle")
68 | .font(.title)
69 | .foregroundColor(Color("bg"))
70 | }.background(
71 | NavigationLink(
72 | destination: ContactsView()
73 | .environmentObject(contactStore)
74 | .edgesIgnoringSafeArea(.bottom)
75 | .onAppear(perform: {
76 | appState.tabBarIsHidden = true
77 | self.moveToContacts = false
78 | })
79 | .onDisappear(perform: {
80 | appState.tabBarIsHidden = false
81 | }),
82 | isActive: $moveToContacts
83 | ) {
84 | EmptyView()
85 | }
86 | .navigationBarTitle("Contacts")
87 | )
88 | }
89 |
90 | }
91 |
92 |
93 | struct ConversationList_Previews: PreviewProvider {
94 | static var previews: some View {
95 | ConversationList()
96 | .environmentObject(AppState())
97 | .environmentObject(ConversationViewModel())
98 | }
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/AddaMeIOS/Conversation/ConversationRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConversationRow.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 05.09.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | struct ConversationRow: View {
12 |
13 | let image = ImageDraw()
14 | var conversation: ConversationResponse.Item
15 |
16 | @Environment(\.imageCache) var cache: ImageCache
17 | @Environment(\.presentationMode) var presentationMode
18 | @Environment(\.colorScheme) var colorScheme
19 |
20 | var body : some View {
21 | Group {
22 | HStack(spacing: 0) {
23 | if conversation.lastMessage?.sender.avatarUrl != nil {
24 | AsyncImage(
25 | urlString: conversation.lastMessage?.sender.avatarUrl,
26 | placeholder: {
27 | Text("Loading...").frame(width: 100, height: 100, alignment: .center)
28 | },
29 | image: {
30 | Image(uiImage: $0).resizable()
31 | }
32 | )
33 | .aspectRatio(contentMode: .fit)
34 | .frame(width: 50, height: 50)
35 | .clipShape(Circle())
36 | .padding(.trailing, 5)
37 | } else {
38 | Image(uiImage: image.random())
39 | .aspectRatio(contentMode: .fit)
40 | .frame(width: 50, height: 50)
41 | .clipShape(Circle())
42 | }
43 |
44 | VStack(alignment: .leading, spacing: 5) {
45 | Text(conversation.title)
46 | .lineLimit(1)
47 | .font(.system(size: 18, weight: .semibold, design: .rounded))
48 | .foregroundColor(Color(.systemBlue))
49 |
50 | if conversation.lastMessage != nil {
51 | Text(conversation.lastMessage!.messageBody).lineLimit(1)
52 | }
53 | }
54 | .padding(5)
55 |
56 | Spacer(minLength: 3)
57 |
58 | VStack(alignment: .trailing, spacing: 5) {
59 | if conversation.lastMessage != nil {
60 | Text("\(conversation.lastMessage!.createdAt?.dateFormatter ?? String.empty)")
61 | }
62 |
63 | if conversation.lastMessage?.messageBody != String.empty {
64 | //Text("6").padding(8).background(Color("bg"))
65 | // .foregroundColor(.white).clipShape(Circle())
66 | } else {
67 | Spacer()
68 | }
69 | }
70 |
71 | }
72 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 20, alignment: .leading)
73 | .padding(2)
74 | }
75 | }
76 | }
77 |
78 | //struct ConversationRow_Previews: PreviewProvider {
79 | // static var previews: some View {
80 | // ConversationRow(conversation: demoConversations[0])
81 | // }
82 | //}
83 |
--------------------------------------------------------------------------------
/AddaMeIOS/CoreDataEntities/ContactEntity+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactEntity+CoreDataClass.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 01.12.2020.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(ContactEntity)
13 | public class ContactEntity: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/AddaMeIOS/CoreDataEntities/ContactEntity+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactEntity+CoreDataProperties.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 01.12.2020.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension ContactEntity {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "ContactEntity")
17 | }
18 |
19 | @NSManaged public var avatar: String?
20 | @NSManaged public var fullName: String
21 | @NSManaged public var id: String?
22 | @NSManaged public var identifier: String
23 | @NSManaged public var isRegister: Bool
24 | @NSManaged public var phoneNumber: String
25 | @NSManaged public var userId: String
26 |
27 | }
28 |
29 | extension ContactEntity : Identifiable {
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/AddaMeIOS/CoreDataEntities/ContactEntityExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactEntityExtension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 01.12.2020.
6 | //
7 |
8 | import Foundation
9 | import CoreData
10 | extension ContactEntity {
11 | static func allContactsFetchRequest() -> NSFetchRequest {
12 | let request: NSFetchRequest = ContactEntity.fetchRequest()
13 | request.sortDescriptors = [NSSortDescriptor(key: "phoneNumber", ascending: true)]
14 | print(#line, request)
15 | return request
16 | }
17 | }
18 |
19 | extension ContactEntity {
20 | static var registerContactsFetchRequest: NSFetchRequest {
21 | let request: NSFetchRequest = ContactEntity.fetchRequest()
22 | request.predicate = NSPredicate(format: "isRegister == true")
23 | request.sortDescriptors = [NSSortDescriptor(key: "fullName", ascending: true)]
24 |
25 | return request
26 | }
27 | }
28 |
29 | extension ContactEntity: ManagedModel {
30 | public static var defaultPredicate: NSPredicate { return NSPredicate(value: true) }
31 |
32 | static func findOrCreate(withData data: APIData, in context: NSManagedObjectContext) -> ContactEntity {
33 |
34 | guard let content = data as? Contact else {
35 | fatalError("Incorrent API response")
36 | }
37 |
38 | let predicate = NSPredicate(format: "%K == %@", #keyPath(phoneNumber), content.phoneNumber)
39 | let contact = ContactEntity.findOrCreate(in: context, matching: predicate) { contact in
40 | contact.id = content.id ?? String.empty
41 | contact.fullName = content.fullName ?? String.empty
42 | contact.avatar = content.avatar
43 | contact.identifier = content.identifier
44 | contact.isRegister = content.isRegister ?? false
45 | contact.userId = content.userId ?? String.empty
46 | contact.phoneNumber = content.phoneNumber
47 | }
48 |
49 | contact.id = content.id ?? String.empty
50 | contact.fullName = content.fullName ?? String.empty
51 | contact.avatar = content.avatar
52 | contact.identifier = content.identifier
53 | contact.isRegister = content.isRegister ?? false
54 | contact.userId = content.userId ?? String.empty
55 | contact.phoneNumber = content.phoneNumber
56 |
57 | return contact
58 | }
59 |
60 | public static var defaultSortDescriptors: [NSSortDescriptor] {
61 | return [NSSortDescriptor(key: #keyPath(isRegister), ascending: true)]
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/AddaMeIOS/CoreDataModels/AddaModel.xcdatamodeld/AddaModel.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/AddaMeIOS/CoreDataModels/Contacts.xcdatamodeld/Contacts.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/AddaMeIOS/Environment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Environment.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 02.11.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum EnvironmentKeys {
11 | // MARK: - Keys
12 | private enum Keys {
13 | enum Plist {
14 | static let rootURL = "ROOT_URL"
15 | static let webSocketURL = "WEB_SOCKET_URL"
16 | static let accessKeyId = "ACCESS_KEY_ID"
17 | static let secretAccessKey = "SECRET_ACCESS_KEY"
18 | }
19 | }
20 |
21 | // MARK: - Plist
22 | private static let infoDictionary: [String: Any] = {
23 |
24 | guard let dict = Bundle.main.infoDictionary else {
25 | fatalError("Plist file not found")
26 | }
27 |
28 | return dict
29 | }()
30 |
31 | // MARK: - Plist values
32 | static let rootURL: URL = {
33 |
34 | guard let rootURLstring = EnvironmentKeys.infoDictionary["ROOT_URL"] as? String else {
35 | fatalError("Root URL not set in plist for this environment")
36 | }
37 |
38 | guard let url = URL(string: rootURLstring) else {
39 | fatalError("Root URL is invalid")
40 | }
41 |
42 | return url
43 | }()
44 |
45 | static let webSocketURL: URL = {
46 |
47 | guard let webSocketString = EnvironmentKeys.infoDictionary[Keys.Plist.webSocketURL] as? String else {
48 | fatalError("WEB SOCKET URL Key not set in plist for this environment")
49 | }
50 |
51 | guard let url = URL(string: webSocketString) else {
52 | fatalError("WEB SOCKET URL is invalid")
53 | }
54 |
55 | return url
56 | }()
57 |
58 | static let accessKeyId: String = {
59 |
60 | guard let accessKeyId = EnvironmentKeys.infoDictionary["ACCESS_KEY_ID"] as? String else {
61 | fatalError("ACCESS_KEY_ID not set in plist for this environment")
62 | }
63 |
64 | return accessKeyId
65 | }()
66 |
67 | static let secretAccessKey: String = {
68 |
69 | guard let secretAccessKey = EnvironmentKeys.infoDictionary["SECRET_ACCESS_KEY"] as? String else {
70 | fatalError("SECRET_ACCESS_KEY not set in plist for this environment")
71 | }
72 |
73 | return secretAccessKey
74 | }()
75 |
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/AddaMeIOS/ExprimentViews/Navigation_WithList.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Navigation_WithList.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 16.10.2020.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 | import MapKit
11 |
12 | class TestViewModel: ObservableObject {
13 | @Published var questions = [Int: String]()
14 |
15 | var count: Int = 0
16 |
17 | init() {
18 | self.questions = [
19 | 0: "Hello",
20 | 1: "Private",
21 | 2: "Asalamualikum"
22 | ]
23 | }
24 |
25 | func add() {
26 | count = self.questions.count
27 | count += 1
28 | self.questions.updateValue("Hello Alif \(count)", forKey: 0)
29 | self.questions[count] = "Hi \(count)"
30 | }
31 | }
32 |
33 | struct Navigation_WithList: View {
34 |
35 | @ObservedObject var data: TestViewModel = .init()
36 |
37 | var body: some View {
38 | NavigationView {
39 | ScrollView {
40 | LazyVStack {
41 | ForEach(data.questions.map { $1 }.sorted() , id: \.self) { datum in
42 | NavigationLink(destination: ShoppingDetail(shoppingItem: datum)) {
43 | Text(datum).font(Font.system(size: 24)).padding()
44 | }
45 | }
46 | .listStyle(GroupedListStyle())
47 | .navigationTitle("Shopping")
48 | .toolbar {
49 | ToolbarItem {
50 | Button("Add", action: {self.data.add()})
51 | }
52 |
53 | }
54 | }
55 | }
56 | }
57 |
58 | }
59 |
60 | }
61 |
62 |
63 | struct Navigation_WithList_Previews: PreviewProvider {
64 | static var previews: some View {
65 | Navigation_WithList()
66 | }
67 | }
68 |
69 |
70 | struct ShoppingDetail: View {
71 | var shoppingItem: String!
72 | var body: some View {
73 | VStack {
74 | Text("Shopping List Details")
75 | .font(.title)
76 | .frame(maxWidth: .infinity)
77 | .padding()
78 | .background(Color("Theme3ForegroundColor"))
79 | .foregroundColor(Color("Theme3BackgroundColor"))
80 | Spacer()
81 | Text(shoppingItem).font(.title)
82 | Spacer()
83 |
84 | }.navigationTitle(shoppingItem)
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/AddaMeIOS/ExprimentViews/TabBarTopView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarTopView.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 03.09.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TabBarTopView: View {
11 | @State var search = String.empty
12 | @Binding var expand: Bool
13 | @Binding var searchExpand: Bool
14 |
15 | var body: some View {
16 | VStack(spacing: 20) {
17 |
18 | if self.expand {
19 |
20 | ScrollView(.horizontal, showsIndicators: false) {
21 |
22 | HStack(spacing: 18) {
23 |
24 | Button(action: {
25 |
26 | }) {
27 | Image(systemName: "plus")
28 | .resizable()
29 | .frame(width: 25, height: 25)
30 | .foregroundColor(Color.yellow)
31 | .padding(18)
32 |
33 | }.background(Color.red)
34 | .clipShape(Circle())
35 |
36 | ForEach(1...7, id: \.self) { i in
37 |
38 | Button(action: {
39 |
40 | }) {
41 | Image(systemName: "ant.circle")
42 | .resizable()
43 | .renderingMode(.original)
44 | .frame(width: 60, height: 60)
45 | }
46 | }
47 | }
48 | }
49 |
50 | }
51 |
52 | if self.searchExpand {
53 | HStack(spacing: 15){
54 |
55 | Image(systemName: "magnifyingglass")
56 | .resizable()
57 | .frame(width: 18, height: 18)
58 | .foregroundColor(Color.black.opacity(0.3))
59 |
60 | TextField("Search", text: self.$search)
61 |
62 | }.padding()
63 | .background(Color.white)
64 | .cornerRadius(8)
65 | .padding(.bottom, 10)
66 | }
67 | }.padding()
68 | .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
69 | .background(Color.gray.opacity(0.1))
70 | .clipShape(shape())
71 | .animation(.default)
72 | }
73 | }
74 |
75 | var globalBool: Bool = true {
76 | didSet {
77 | // This will get called
78 | NSLog("Did Set" + globalBool.description)
79 | }
80 | }
81 |
82 | struct TabBarTopView_Previews: PreviewProvider {
83 |
84 | static var previews: some View {
85 | TabBarTopView(expand:
86 | Binding(get: { globalBool }, set: { globalBool = $0 }),
87 | searchExpand: Binding(get: { globalBool }, set: { globalBool = $0 })
88 | )
89 | }
90 | }
91 |
92 | struct shape: Shape {
93 | func path(in rect: CGRect) -> Path {
94 | let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 22, height: 22) )
95 |
96 | return Path(path.cgPath)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/Array+RemoveDuplicate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+RemoveDuplicate.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 17.10.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Array where Element: Hashable {
11 | func uniqElemets() -> [Element] {
12 | let set = Set(self)
13 | return Array(set)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/AssetExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AssetExtractor.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 18.10.2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class AssetExtractor {
11 |
12 | static func createLocalUrl(forImageNamed name: String) -> URL? {
13 |
14 | let fileManager = FileManager.default
15 | let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
16 | let url = cacheDirectory.appendingPathComponent("\(name).png")
17 |
18 | guard fileManager.fileExists(atPath: url.path) else {
19 | guard
20 | let image = UIImage(named: name),
21 | let data = image.pngData()
22 | else {
23 | print(#line, self, "cant find image by name: \(name)")
24 | return nil
25 |
26 | }
27 |
28 | fileManager.createFile(atPath: url.path, contents: data, attributes: nil)
29 | return url
30 | }
31 |
32 | return url
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/Codable+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Codable+Extension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 28.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Encodable {
11 | var jsonData: Data? {
12 | let encoder = JSONEncoder()
13 | encoder.dateEncodingStrategy = .iso8601
14 | do {
15 | return try encoder.encode(self)
16 | } catch {
17 | print(error.localizedDescription)
18 | return nil
19 | }
20 | }
21 |
22 | var jsonString: String? {
23 | guard let data = self.jsonData else { return nil }
24 | return String(data: data, encoding: .utf8)
25 | }
26 | }
27 |
28 | extension Decodable {
29 | static func from(json: String, using encoding: String.Encoding = .utf8) -> Self? {
30 | guard let data = json.data(using: encoding) else { return nil }
31 | return Self.from(data: data)
32 | }
33 |
34 | static func from(data: Data) -> Self? {
35 | do {
36 | let jsonDecoder = JSONDecoder()
37 | jsonDecoder.dateDecodingStrategy = .iso8601
38 | return try JSONDecoder().decode(Self.self, from: data)
39 | } catch {
40 | print(error.localizedDescription)
41 | return nil
42 | }
43 | }
44 |
45 | static func decode(json: String, using usingForWebRtcingEncoding: String.Encoding = .utf8) -> Self? {
46 | guard let data = json.data(using: usingForWebRtcingEncoding) else { return nil }
47 | return Self.decode(data: data)
48 | }
49 |
50 | static func decode(data usingForWebRtcingData: Data) -> Self? {
51 | let decoder = JSONDecoder()
52 | decoder.dateDecodingStrategy = .iso8601
53 |
54 | do {
55 | return try decoder.decode(Self.self, from: usingForWebRtcingData)
56 | } catch {
57 | print(#line, error)
58 | return nil
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/DataExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataExtension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 29.11.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Data {
11 | func toHexString() -> String {
12 | return map { String(format: "%02.2hhx", $0) }.joined()
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/Date/Date+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+Extension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 03.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Date {
11 | var toISO8601String: String? {
12 | ISO8601DateFormatter().string(from: self)
13 | }
14 |
15 | func getFormattedDate(format: String) -> String {
16 | let dateformat = DateFormatter()
17 | dateformat.locale = Locale.current
18 | dateformat.dateFormat = format
19 | return dateformat.string(from: self)
20 | }
21 |
22 | var hour : Int {
23 | let components = Calendar.current.dateComponents([.hour ], from: self)
24 | return components.hour ?? 0
25 | }
26 |
27 | var minute : Int {
28 | let components = Calendar.current.dateComponents([.minute ], from: self)
29 | return components.minute ?? 0
30 | }
31 |
32 | var hourMinuteString : String {
33 | let formatter = DateFormatter()
34 | formatter.dateFormat = "HH:mm"
35 | return formatter.string(from: self)
36 | }
37 |
38 | var dayMonthYear : String {
39 | let formatter = DateFormatter()
40 | formatter.dateFormat = "dd.MM.yyyy"
41 | return formatter.string(from: self)
42 | }
43 |
44 | var dateFormatter: String {
45 |
46 | let dateFormatter = DateFormatter()
47 | dateFormatter.locale = Locale.autoupdatingCurrent
48 |
49 | let timeSinceDateInSconds = Date().timeIntervalSince(self)
50 | let secondInDay: TimeInterval = 24*60*60
51 |
52 | if timeSinceDateInSconds > 7 * secondInDay {
53 | dateFormatter.dateFormat = "MM/dd/yy"
54 | } else if timeSinceDateInSconds > secondInDay {
55 | dateFormatter.dateFormat = "EEEE"
56 | } else {
57 | dateFormatter.timeStyle = .short
58 | dateFormatter.string(from: self)
59 | }
60 |
61 | return dateFormatter.string(from: self)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/Date/DateFormatter+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateFormatter+Extension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 03.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension DateFormatter {
11 | static var iso8601DateFormatter: DateFormatter = {
12 | let dateFormatter = DateFormatter()
13 | dateFormatter.locale = Locale.current
14 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
15 | return dateFormatter
16 | }()
17 | }
18 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/Image+Compress.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Image+Compress.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 19.11.2020.
6 | //
7 |
8 | import UIKit
9 | import AVFoundation
10 |
11 | extension UIImage {
12 | enum JPEGQuality: CGFloat {
13 | case lowest = 0
14 | case low = 0.25
15 | case medium = 0.5
16 | case high = 0.75
17 | case highest = 1
18 | }
19 |
20 | func compressImage(_ compressionQuality: JPEGQuality? = .medium) -> (Data?, String) {
21 | var unsuported = false
22 | var imageData = Data()
23 | var imageFormat = "jpeg"
24 |
25 | do {
26 | let data = try self.heicData(compressionQuality: compressionQuality!)
27 | imageData = data
28 | imageFormat = "heic"
29 | } catch {
30 | print("Error creating HEIC data: \(error.localizedDescription)")
31 | unsuported = true
32 | }
33 |
34 | if unsuported == true {
35 |
36 | guard let data = self.jpegData(compressionQuality: compressionQuality!.rawValue) else {
37 | return (nil, imageFormat)
38 | }
39 |
40 | imageData = data
41 |
42 | }
43 |
44 | return (imageData, imageFormat)
45 |
46 | }
47 |
48 | }
49 |
50 | extension UIImage {
51 | enum HEICError: Error {
52 | case heicNotSupported
53 | case cgImageMissing
54 | case couldNotFinalize
55 | }
56 |
57 | func heicData(compressionQuality: JPEGQuality) throws -> Data {
58 | let data = NSMutableData()
59 | guard let imageDestination =
60 | CGImageDestinationCreateWithData(
61 | data, AVFileType.heic as CFString, 1, nil
62 | )
63 | else {
64 | throw HEICError.heicNotSupported
65 | }
66 |
67 | guard let cgImage = self.cgImage else {
68 | throw HEICError.cgImageMissing
69 | }
70 |
71 | let options: NSDictionary = [
72 | kCGImageDestinationLossyCompressionQuality: compressionQuality.rawValue
73 | ]
74 |
75 | CGImageDestinationAddImage(imageDestination, cgImage, options)
76 | guard CGImageDestinationFinalize(imageDestination) else {
77 | throw HEICError.couldNotFinalize
78 | }
79 |
80 | return data as Data
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/Publishers+Keyboard.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Publishers+Keyboard.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 13.09.2020.
6 | //
7 |
8 | import Combine
9 | import UIKit
10 |
11 | extension Publishers {
12 | static var keyboardHeight: AnyPublisher {
13 | let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
14 | .map { $0.keyboardHeight }
15 |
16 | let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
17 | .map { _ in CGFloat(0) }
18 |
19 | return MergeMany(willShow, willHide)
20 | .eraseToAnyPublisher()
21 | }
22 | }
23 |
24 | extension Notification {
25 | var keyboardHeight: CGFloat {
26 | return (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/RangeReplaceableCollection+AppendIfNotContains.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RangeReplaceableCollection+AppendIfNotContains.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 22.10.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension RangeReplaceableCollection where Element: Equatable {
11 | @discardableResult
12 | mutating func appendIfNotContains(_ element: Element) -> (appended: Bool, memberAfterAppend: Element) {
13 | if let index = firstIndex(of: element) {
14 | return (false, self[index])
15 | } else {
16 | append(element)
17 | return (true, element)
18 | }
19 | }
20 | }
21 |
22 | extension RangeReplaceableCollection where Element: Equatable {
23 | mutating func prependUnique(_ element: Element) {
24 | if let index = firstIndex(of: element) {
25 | remove(at: index)
26 | }
27 | insert(element, at: startIndex)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/String+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 18.11.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | static var empty: String { return "" }
12 | }
13 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/String/String+CodingKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+CodingKey.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 28.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String: CodingKey {
11 | public var stringValue: String {
12 | return self
13 | }
14 |
15 | public var intValue: Int? {
16 | return Int(self)
17 | }
18 |
19 | public init?(intValue: Int) {
20 | self.init(intValue.description)
21 | }
22 |
23 | public init?(stringValue: String) {
24 | self.init(stringValue)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/String/String+Data.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Data.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 02.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | var stringToData: Data? {
12 | return Data(
13 | base64Encoded: self,
14 | options: Data.Base64DecodingOptions.ignoreUnknownCharacters
15 | ) ?? nil
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/String/String+Date.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Date.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 29.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | var toISO8601Date: Date? {
12 | ISO8601DateFormatter().date(from: self)
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/String/String+ReplaceCharcters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Replace.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 24.10.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | func replace(target: String, withString: String) -> String {
12 | return self.replacingOccurrences(of: target, with: withString, options: NSString.CompareOptions.literal, range: nil)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/UIApplicationKeyboardOnDragGestureExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIApplicationKeyboardOnDragGestureExtension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 30.11.2020.
6 | //
7 |
8 | import SwiftUI
9 | import Foundation
10 |
11 | extension UIApplication {
12 | func endEditing(_ force: Bool) {
13 | self.windows
14 | .filter{$0.isKeyWindow}
15 | .first?
16 | .endEditing(force)
17 | }
18 | }
19 |
20 | struct ResignKeyboardOnDragGesture: ViewModifier {
21 | var gesture = DragGesture().onChanged{_ in
22 | UIApplication.shared.endEditing(true)
23 | }
24 | func body(content: Content) -> some View {
25 | content.gesture(gesture)
26 | }
27 | }
28 |
29 | extension View {
30 | func resignKeyboardOnDragGesture() -> some View {
31 | return modifier(ResignKeyboardOnDragGesture())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/UIImageView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Extension.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 30.08.2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | //extension UIImageView {
12 | // func loadImage(at url: URL) {
13 | // UIImageLoader.loader.load(url, for: self)
14 | // }
15 | //
16 | // func cancelImageLoad() {
17 | // UIImageLoader.loader.cancel(for: self)
18 | // }
19 | //}
20 |
--------------------------------------------------------------------------------
/AddaMeIOS/Extension/View/View+Extension+HideRowSeparatorModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Extension+HideRowSeparatorModifier.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 27.10.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct HideRowSeparatorModifier: ViewModifier {
11 |
12 | static let defaultListRowHeight: CGFloat = 44
13 | var insets: EdgeInsets
14 | var background: Color
15 |
16 | init(insets: EdgeInsets, background: Color) {
17 | self.insets = insets
18 | var alpha: CGFloat = 0
19 | UIColor(background).getWhite(nil, alpha: &alpha)
20 | assert(alpha == 1, "Setting background to a non-opaque color will result in separators remaining visible.")
21 | self.background = background
22 | }
23 |
24 | func body(content: Content) -> some View {
25 | content
26 | .padding(insets)
27 | .frame(
28 | minWidth: 0, maxWidth: .infinity,
29 | minHeight: Self.defaultListRowHeight,
30 | alignment: .leading
31 | )
32 | .listRowInsets(EdgeInsets())
33 | .background(background)
34 | }
35 | }
36 |
37 | extension EdgeInsets {
38 | static let defaultListRowInsets = Self(top: 0, leading: 16, bottom: 0, trailing: 16)
39 | }
40 |
41 | extension View {
42 | func hideRowSeparator(
43 | insets: EdgeInsets = .defaultListRowInsets,
44 | background: Color = Color(.systemBackground)
45 | ) -> some View {
46 | modifier(HideRowSeparatorModifier(
47 | insets: insets,
48 | background: background
49 | ))
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/AppUserDefaults.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppUserDefaults.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 08.11.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | enum AppUserDefaults {}
11 |
12 | extension AppUserDefaults {
13 | enum Key: String {
14 | case isAuthorized
15 | case currentUser
16 | case token
17 | case cllocation
18 | case distance
19 | case isUserFristNameUpdated
20 | }
21 | }
22 |
23 | extension AppUserDefaults {
24 |
25 | static func save(_ value: Any?, forKey key: Key) {
26 | if value is [Any] {
27 | UserDefaults.standard.setValue(value, forKey: key.rawValue)
28 | } else {
29 | UserDefaults.standard.set(value, forKey: key.rawValue)
30 | }
31 | UserDefaults.standard.synchronize()
32 | }
33 |
34 | static func saveCodable(_ value: T?, forKey key: Key) {
35 | let data = try? JSONEncoder().encode(value)
36 | UserDefaults.standard.set(data, forKey: key.rawValue)
37 | UserDefaults.standard.synchronize()
38 | }
39 |
40 | static func saveCodable(_ value: [T], forKey key: Key) {
41 | let data = try? JSONEncoder().encode(value)
42 | UserDefaults.standard.set(data, forKey: key.rawValue)
43 | UserDefaults.standard.synchronize()
44 | }
45 |
46 | static func value(forKey key: Key) -> T? where T: Decodable {
47 | guard let encodedData = UserDefaults.standard.data(forKey: key.rawValue) else {
48 | return nil
49 | }
50 | return try? JSONDecoder().decode(T.self, from: encodedData)
51 | }
52 |
53 | static func value(forKey key: Key) -> [T] where T: Decodable {
54 | guard let encodedData = UserDefaults.standard.data(forKey: key.rawValue) else {
55 | return []
56 | }
57 | return (try? JSONDecoder().decode(Array.self, from: encodedData)) ?? []
58 | }
59 |
60 | static func removeValue(forKey key: Key) {
61 | UserDefaults.standard.removeObject(forKey: key.rawValue)
62 | UserDefaults.standard.synchronize()
63 | }
64 |
65 | static func saveCurrentUserAndToken(_ res: LoginRes) {
66 | KeychainService.save(codable: res.user, for: .currentUser)
67 | KeychainService.save(codable: res.access, for: .token)
68 | }
69 |
70 | static func resetAuthData() {
71 | save(false, forKey: .isAuthorized)
72 | KeychainService.save(codable: CurrentUser?.none, for: .currentUser)
73 | KeychainService.save(codable: Access?.none, for: .token)
74 | KeychainService.logout()
75 | erase()
76 | }
77 |
78 | static func erase() {
79 | let appDomain = Bundle.main.bundleIdentifier!
80 | UserDefaults.standard.removePersistentDomain(forName: appDomain)
81 | UserDefaults.standard.synchronize()
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/AwsS3Manager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AwsS3Manager.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 18.11.2020.
6 | //
7 |
8 | import UIKit
9 | import S3
10 | import NIO
11 | import AVFoundation
12 |
13 | class AWSS3Helper {
14 |
15 | static var bucketWithEndpoint = "https://adda.nyc3.digitaloceanspaces.com/"
16 | static private let compressionQueue = OperationQueue()
17 |
18 | static var getCurrentMillis: Int64 {
19 | return Int64(Date().timeIntervalSince1970 * 1000)
20 | }
21 |
22 | static func uploadImage(
23 | _ image: UIImage,
24 | conversationId: String? = nil,
25 | userId: String? = nil,
26 | completion: @escaping (String?) -> ()) {
27 |
28 | guard let currentUSER : CurrentUser = KeychainService.loadCodable(for: .currentUser) else {
29 | print(#line, "Missing current user from KeychainService")
30 | return
31 | }
32 |
33 | let s3 = S3.init(
34 | accessKeyId: EnvironmentKeys.accessKeyId,
35 | secretAccessKey: EnvironmentKeys.secretAccessKey,
36 | region: .useast1, endpoint: "https://nyc3.digitaloceanspaces.com"
37 | )
38 |
39 |
40 | let data = image.compressImage(conversationId == nil ? .highest : .medium)
41 | let imageFormat = data.1
42 | guard let imageData = data.0 else {
43 | completion(nil)
44 | return
45 | }
46 |
47 | let currentTime = AWSS3Helper.getCurrentMillis
48 | var imageKey = String(format: "%ld", currentTime)
49 | if conversationId != nil {
50 | imageKey = "uploads/images/\(conversationId!)/\(imageKey).\(imageFormat)"
51 | } else if userId != nil {
52 | imageKey = "uploads/images/\(userId!)/\(imageKey).\(imageFormat)"
53 | } else {
54 | imageKey = "uploads/images/\(currentUSER.id)_\(imageKey).\(imageFormat) "
55 | }
56 |
57 | // Put an Object
58 | let putObjectRequest = S3.PutObjectRequest(
59 | acl: .publicRead,
60 | body: imageData,
61 | bucket: "adda",
62 | contentLength: Int64(imageData.count),
63 | key: imageKey
64 | )
65 |
66 | let futureOutput = s3.putObject(putObjectRequest)
67 |
68 | futureOutput.whenSuccess({ (response) in
69 | print(#line, self, response, imageKey)
70 | let finalURL = bucketWithEndpoint + imageKey
71 | completion(finalURL)
72 | })
73 |
74 | futureOutput.whenFailure({ (error) in
75 | print(#line, self, error)
76 | completion(nil)
77 | })
78 |
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Bundle+Decode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Decode.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 06.11.2020.
6 | //
7 |
8 | import UIKit
9 |
10 | extension Bundle {
11 | func decode(_ type: T.Type, from file: String) -> T {
12 | guard let url = self.url(forResource: file, withExtension: nil) else {
13 | fatalError("Failed to locate \(file) in bundle.")
14 | }
15 |
16 | guard let data = try? Data(contentsOf: url) else {
17 | fatalError("Failed to load \(file) from bundle.")
18 | }
19 |
20 | let decoder = JSONDecoder()
21 |
22 | guard let loaded = try? decoder.decode(T.self, from: data) else {
23 | fatalError("Failed to decode \(file) from bundle.")
24 | }
25 |
26 | return loaded
27 | }
28 | }
29 |
30 | // will use featch demo data
31 | //func load() {
32 | // DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
33 | // let sections = Bundle.main.decode([MenuSection].self, from: "menu.json")
34 | // self.sections = sections
35 | // self.representativeSample = [sections[0].items[0], sections[1].items[2], sections[2].items[2]]
36 | // }
37 | //}
38 |
39 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/DateHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateHelper.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 03.09.2020.
6 | //
7 |
8 | import Foundation
9 |
10 | public class DateHelper {
11 | private static let formatter = DateFormatter()
12 | private static let timeZone = TimeZone.current
13 | private static let timeLocale = Locale.current
14 |
15 | public static func fromISO8601String(_ dateString: String) -> Date? {
16 | formatter.timeZone = TimeZone.current
17 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
18 | return formatter.date(from: dateString)
19 | }
20 |
21 | public static func toISO8601String(_ date: Date) -> String {
22 | formatter.timeZone = TimeZone.current
23 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
24 | return formatter.string(from: date)
25 | }
26 |
27 | static func dateFromString(_ dateAsString: String?) -> Date? {
28 | guard let string = dateAsString else { return nil }
29 | formatter.timeZone = TimeZone.current
30 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSS"
31 | let val = formatter.date(from: string)
32 | return val
33 | }
34 |
35 | static func dateToString(_ dateIn: Date?) -> String? {
36 | guard let date = dateIn else { return nil }
37 | formatter.timeZone = TimeZone.current
38 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSS"
39 | let val = formatter.string(from: date)
40 | return val
41 | }
42 |
43 | static func getDateFromTimeStamp(timeStamp : Double) -> String {
44 |
45 | let date = NSDate(timeIntervalSince1970: timeStamp)
46 | let dateFormatter = DateFormatter()
47 | dateFormatter.timeStyle = DateFormatter.Style.none //Set time style
48 | dateFormatter.dateStyle = DateFormatter.Style.short
49 |
50 | // UnComment below to get only time
51 | // dayTimePeriodFormatter.dateFormat = "hh:mm a"
52 |
53 | let dateString = dateFormatter.string(from: date as Date)
54 | return dateString
55 | }
56 |
57 | static func dateFormatter(timestamp: Double?) -> String? {
58 | if let timestamp = timestamp {
59 | let date = Date(timeIntervalSinceReferenceDate: timestamp)
60 | let dateFormatter = DateFormatter()
61 | dateFormatter.locale = Locale.autoupdatingCurrent
62 | let timeSinceDateInSconds = Date().timeIntervalSince(date)
63 | let secondInDays: TimeInterval = 24*60*60
64 |
65 | if timeSinceDateInSconds > 7 * secondInDays {
66 | dateFormatter.dateFormat = "MM/dd/yy"
67 | } else if timeSinceDateInSconds > secondInDays {
68 | dateFormatter.dateFormat = "EEEE"
69 | } else {
70 | dateFormatter.dateFormat = "HH:mm"
71 | }
72 | return dateFormatter.string(from: date)
73 | } else {
74 | return nil
75 | }
76 | }
77 | }
78 |
79 | extension Date {
80 | var millisecondsSince1970: Int64 {
81 | return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
82 | }
83 |
84 | init(milliseconds: Int64) {
85 | self = Date(timeIntervalSince1970: TimeInterval(milliseconds / 1000))
86 | }
87 |
88 | func adding(minutes: Int) -> Date {
89 | return Calendar.current.date(byAdding: .minute, value: minutes, to: self)!
90 | }
91 |
92 | }
93 |
94 | let mediaDurationFormatter: DateComponentsFormatter = {
95 | let formatter = DateComponentsFormatter()
96 | formatter.allowedUnits = [.minute, .second]
97 | formatter.zeroFormattingBehavior = [.pad]
98 | formatter.unitsStyle = .positional
99 | return formatter
100 | }()
101 |
102 | let millisecondsPerSecond: Double = 1000
103 | //let nanosecondsPerSecond: CMTimeScale = 1000000000
104 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Image/AsyncImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncImage.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 31.08.2020.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | struct AsyncImage: View {
12 |
13 | @StateObject private var loader: ImageLoader
14 | private var placeholder: Placeholder
15 | private var image: (UIImage) -> Image
16 |
17 | init(
18 | url: URL,
19 | @ViewBuilder placeholder: () -> Placeholder,
20 | @ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
21 | ) {
22 | self.placeholder = placeholder()
23 | self.image = image
24 | _loader = StateObject(wrappedValue: ImageLoader(url: url, cache: Environment(\.imageCache).wrappedValue))
25 | }
26 |
27 | init(
28 | urlString: String?,
29 | @ViewBuilder placeholder: () -> Placeholder,
30 | @ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
31 | ) {
32 |
33 | var url = URL(string: String.empty)
34 | if urlString == nil {
35 | if let fileURL = AssetExtractor.createLocalUrl(forImageNamed: "Avatar") {
36 | url = fileURL
37 | }
38 | } else {
39 | url = URL(string: urlString!)!
40 | }
41 |
42 | self.placeholder = placeholder()
43 | self.image = image
44 | _loader = StateObject(
45 | wrappedValue: ImageLoader(
46 | url: url!,
47 | cache: Environment(\.imageCache).wrappedValue
48 | )
49 | )
50 | }
51 |
52 | var body: some View {
53 | content
54 | .onAppear(perform: loader.load)
55 | }
56 |
57 | private var content: some View {
58 | Group {
59 | if loader.image != nil {
60 | image(loader.image!)
61 | } else {
62 | placeholder
63 | }
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Image/EnvironmentValues+ImageCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnvironmentValues+ImageCache.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 31.08.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ImageCacheKey: EnvironmentKey {
11 | static let defaultValue: ImageCache = TemporaryImageCache()
12 | }
13 |
14 | extension EnvironmentValues {
15 | var imageCache: ImageCache {
16 | get { self[ImageCacheKey.self] }
17 | set { self[ImageCacheKey.self] = newValue }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Image/ImageCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageCache.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 31.08.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | protocol ImageCache {
11 | subscript(_ url: URL) -> UIImage? { get set }
12 | }
13 |
14 | struct TemporaryImageCache: ImageCache {
15 |
16 | private let cache = NSCache()
17 |
18 | subscript(key: URL) -> UIImage? {
19 | get { cache.object(forKey: key as NSURL) }
20 | set {
21 | newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Image/ImageDraw.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageDraw.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 22.11.2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class ImageDraw {
12 | private let renderer = UIGraphicsImageRenderer(size: CGSize(width: 400, height: 400))
13 | private var colors = [UIColor.red, UIColor.brown, UIColor.yellow, UIColor.green, UIColor.black, UIColor.blue]
14 |
15 | func random() -> UIImage {
16 |
17 | colors.shuffle()
18 |
19 | let image = renderer.image { (context) in
20 | UIColor.darkGray.setStroke()
21 | context.stroke(renderer.format.bounds)
22 |
23 | let count = 400 / colors.count
24 |
25 | colors.enumerated().forEach { (idx, color) in
26 | color.setFill()
27 | context.fill(CGRect(x: idx * count, y: 0, width: idx * count, height: 400))
28 | }
29 | }
30 |
31 | return image
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Image/ImageLoader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageLoader.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 30.08.2020.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 | import Foundation
11 |
12 | class ImageLoader: ObservableObject {
13 |
14 | @Published var image: UIImage?
15 | private let url: URL
16 | private var cancellable: AnyCancellable?
17 | private var cache: ImageCache?
18 | private(set) var isLodaing = false
19 | private static let imageProcessingQueue = DispatchQueue(label: "image-processing")
20 |
21 | init(url: URL, cache: ImageCache? = nil) {
22 | self.url = url
23 | self.cache = cache
24 | }
25 |
26 | deinit {
27 | cancel()
28 | }
29 |
30 | func load() {
31 |
32 | guard !isLodaing else { return }
33 |
34 | if let image = cache?[url] {
35 | self.image = image
36 | return
37 | }
38 |
39 | cancellable = URLSession.shared.dataTaskPublisher(for: url)
40 | .map { UIImage(data: $0.data) }
41 | .replaceError(with: nil)
42 | .handleEvents(
43 | receiveSubscription: { _ in self.onStart() },
44 | receiveOutput: { self.cache($0) },
45 | receiveCompletion: { _ in self.onFinished() },
46 | receiveCancel: { self.onFinished() }
47 | )
48 | .subscribe(on: Self.imageProcessingQueue)
49 | .receive(on: DispatchQueue.main)
50 | .sink(receiveValue: { [weak self] result in
51 | self?.image = result
52 | })
53 |
54 | }
55 |
56 | func cancel() {
57 | cancellable?.cancel()
58 | }
59 |
60 | private func onStart() {
61 | isLodaing = true
62 | }
63 |
64 | private func onFinished() {
65 | isLodaing = false
66 | }
67 |
68 | private func cache(_ image: UIImage?) {
69 | image.map { cache?[url] = $0 }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Image/ImagePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePicker.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 18.11.2020.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct ImagePicker: UIViewControllerRepresentable {
12 | @Environment(\.presentationMode) var presentationMode
13 | @Binding var image: UIImage?
14 |
15 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController {
16 | let picker = UIImagePickerController()
17 | picker.delegate = context.coordinator
18 | return picker
19 | }
20 |
21 | func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext) {
22 |
23 | }
24 |
25 | func makeCoordinator() -> Coordinator {
26 | Coordinator(self)
27 | }
28 |
29 | class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
30 | let parent: ImagePicker
31 |
32 | init(_ parent: ImagePicker) {
33 | self.parent = parent
34 | }
35 |
36 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
37 |
38 | if let uiImage = info[.originalImage] as? UIImage {
39 | parent.image = uiImage
40 | }
41 |
42 | parent.presentationMode.wrappedValue.dismiss()
43 |
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Keyboard/AdaptsToSoftwareKeyboard.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdaptsToSoftwareKeyboard.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 18.11.2020.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | struct AdaptsToSoftwareKeyboard: ViewModifier {
12 | @State var currentHeight: CGFloat = 0
13 |
14 | func body(content: Content) -> some View {
15 | content
16 | .padding(.bottom, currentHeight)
17 | .animation(.default)
18 | .edgesIgnoringSafeArea(currentHeight == 0 ? [] : .bottom)
19 | .onAppear(perform: subscribeToKeyboardEvents)
20 | }
21 |
22 | private func subscribeToKeyboardEvents() {
23 | NotificationCenter.Publisher(
24 | center: NotificationCenter.default,
25 | name: UIResponder.keyboardWillShowNotification
26 | ).compactMap { notification in
27 | notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect
28 | }.map { rect in
29 | rect.height
30 | }.subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
31 |
32 | NotificationCenter.Publisher(
33 | center: NotificationCenter.default,
34 | name: UIResponder.keyboardWillHideNotification
35 | ).compactMap { notification in
36 | CGFloat.zero
37 | }.subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Keyboard/KeyboardAdaptive.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyboardAdaptive.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 13.09.2020.
6 | //
7 |
8 | import Combine
9 | import SwiftUI
10 |
11 | struct KeyboardAdaptive: ViewModifier {
12 | @State private var bottomPadding: CGFloat = 0
13 |
14 | func body(content: Content) -> some View {
15 | GeometryReader { geometry in
16 | content
17 | .padding(.bottom, self.bottomPadding)
18 | .onReceive(Publishers.keyboardHeight) { keyboardHeight in
19 | let keyboardTop = geometry.frame(in: .global).height - keyboardHeight
20 | let focusedTextInputBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0
21 |
22 | self.bottomPadding = max(0, focusedTextInputBottom - keyboardTop - geometry.safeAreaInsets.bottom)
23 | }
24 | .animation(.easeOut(duration: 0.16))
25 | }
26 | }
27 | }
28 |
29 | extension View {
30 | func keyboardAdaptive() -> some View {
31 | ModifiedContent(content: self, modifier: KeyboardAdaptive())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/Keyboard/UIResponder+Current.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIResponder+Current.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 13.09.2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | extension UIResponder {
12 | static var currentFirstResponder: UIResponder? {
13 | _currentFirstResponder = nil
14 | UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder(_:)), to: nil, from: nil, for: nil)
15 | return _currentFirstResponder
16 | }
17 |
18 | private static weak var _currentFirstResponder: UIResponder?
19 |
20 | @objc private func findFirstResponder(_ sendr: Any) {
21 | UIResponder._currentFirstResponder = self
22 | }
23 |
24 | var globalFrame: CGRect? {
25 | guard let view = self as? UIView else { return nil }
26 | return view.superview?.convert(view.frame, to: nil)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/AddaMeIOS/Helper/ModalView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModalView.swift
3 | // AddaMeIOS
4 | //
5 | // Created by Saroar Khandoker on 14.09.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | private final class Pipe : ObservableObject {
11 | struct Content: Identifiable {
12 | fileprivate typealias ID = String
13 | fileprivate let id = UUID().uuidString
14 | var view: AnyView
15 | }
16 | @Published var content: Content? = nil
17 | }
18 |
19 | public struct ModalPresenter : View where Content : View {
20 | @ObservedObject private var modalView = Pipe()
21 |
22 | private var content: Content
23 |
24 | public init(@ViewBuilder content: () -> Content) {
25 | self.content = content()
26 | }
27 |
28 | public var body: some View {
29 | content
30 | .environmentObject(modalView)
31 | .sheet(item: $modalView.content, content: { $0.view })
32 | }
33 | }
34 |
35 | public struct ModalLink