├── .github └── workflows │ └── swift.yml ├── .gitignore ├── .swift-format ├── .swiftlint.yml ├── Addame.xcodeproj └── project.pbxproj ├── Addame.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ ├── swiftpm │ └── Package.resolved │ └── xcschemes │ ├── AddameCI.xcscheme │ ├── AddameDev.xcscheme │ └── AddamePro.xcscheme ├── Addame ├── Addame.entitlements ├── Addame.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 │ └── Contents.json ├── Configs │ ├── DevelopmentCI.xcconfig │ ├── Environment.swift │ └── ProductionCI.xcconfig ├── Info.plist └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── AddameSPM ├── .gitignore ├── .swiftpm │ └── xcode │ │ ├── package.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ └── xcschemes │ │ ├── AppFeature.xcscheme │ │ ├── AppFeatureTests.xcscheme │ │ ├── AttachmentClient.xcscheme │ │ ├── AuthenticationView.xcscheme │ │ ├── ChatView.xcscheme │ │ ├── ContactClient.xcscheme │ │ ├── ContactClientLive.xcscheme │ │ ├── ContactsViewTests.xcscheme │ │ ├── Event.xcscheme │ │ ├── EventClient.xcscheme │ │ ├── EventClientLive.xcscheme │ │ ├── EventFormView.xcscheme │ │ ├── EventFormViewTests.xcscheme │ │ ├── EventView.xcscheme │ │ ├── EventViewTests.xcscheme │ │ ├── KeychainService.xcscheme │ │ ├── PathMonitorClient.xcscheme │ │ ├── ProfileView.xcscheme │ │ ├── SettingsView.xcscheme │ │ ├── SharedModels.xcscheme │ │ ├── SwiftUIExtension.xcscheme │ │ ├── SwiftUIHelpers.xcscheme │ │ └── TabsView.xcscheme ├── Package.resolved ├── Package.swift ├── README.md ├── Sources │ ├── APIClient │ │ ├── APIClient.swift │ │ ├── URLRequest+Extension.swift │ │ └── UserClient.swift │ ├── AppConfiguration │ │ └── AppConfiguration.swift │ ├── AppFeature │ │ ├── AppDelegate.swift │ │ └── AppView.swift │ ├── AsyncImageLoder │ │ ├── AssetExtractor.swift │ │ ├── AsyncImage.swift │ │ ├── AsyncImageLoder.swift │ │ ├── EnvironmentValues.swift │ │ ├── ImageDraw.swift │ │ ├── ImagePicker.swift │ │ └── TemporaryImageCache.swift │ ├── AttachmentS3Client │ │ ├── AttachmentS3Client.swift │ │ ├── AwsS3Manager.swift │ │ └── Mocks.swift │ ├── AuthenticationView │ │ ├── AuthenticationCore.swift │ │ ├── AuthenticationView.swift │ │ ├── AuthenticationViewExtension.swift │ │ ├── PhoneNumberKit+Extension.swift │ │ ├── PhoneNumberTextFieldView.swift │ │ └── Resources │ │ │ └── PhoneNumberMetadata.json │ ├── ChatClient │ │ ├── ChatClient.swift │ │ └── Mocks.swift │ ├── ChatClientLive │ │ └── Live.swift │ ├── ChatView │ │ ├── ChatAction.swift │ │ ├── ChatBottomView.swift │ │ ├── ChatEnvironment.swift │ │ ├── ChatListView.swift │ │ ├── ChatReducer.swift │ │ ├── ChatRowView.swift │ │ ├── ChatState.swift │ │ └── ChatView.swift │ ├── CombineHelpers │ │ ├── Combine.swift │ │ └── ReplaySubject.swift │ ├── ComposableArchitectureHelpers │ │ └── WebSocketId.swift │ ├── ContactClient │ │ ├── ContactClient.swift │ │ └── Mocks.swift │ ├── ContactClientLive │ │ ├── FlatMapLatest.swift │ │ └── Live.swift │ ├── ContactsView │ │ ├── ContactListAction.swift │ │ ├── ContactListView.swift │ │ ├── ContactRow.swift │ │ ├── ContactRowAction.swift │ │ ├── ContactRowReducer.swift │ │ ├── ContactRowState.swift │ │ ├── ContactsAction.swift │ │ ├── ContactsEnvironment.swift │ │ ├── ContactsReducer.swift │ │ ├── ContactsState.swift │ │ └── ContactsView.swift │ ├── ConversationClient │ │ ├── ConversationClient.swift │ │ └── Mocks.swift │ ├── ConversationClientLive │ │ └── Live.swift │ ├── ConversationsView │ │ ├── ConversationEnvironment.swift │ │ ├── ConversationsAction.swift │ │ ├── ConversationsReducer.swift │ │ ├── ConversationsState.swift │ │ └── ConversationsView.swift │ ├── CoreDataClient │ │ └── CoreDataClient.swift │ ├── CoreDataStore │ │ ├── CoreDataStore.swift │ │ ├── Entities │ │ │ └── Contacts │ │ │ │ ├── ContactEntity+CoreDataClass.swift │ │ │ │ ├── ContactEntity+CoreDataProperties.swift │ │ │ │ └── ContactEntityExtension.swift │ │ ├── Helper │ │ │ ├── CoreDataPublisher.swift │ │ │ ├── ManagedModel.swift │ │ │ └── NSManagedObjectContext+Extensions.swift │ │ └── Models │ │ │ └── AddaModel.xcdatamodeld │ │ │ └── AddaModel.xcdatamodel │ │ │ └── contents │ ├── DeviceClient │ │ └── DeviceClicnt.swift │ ├── EventClient │ │ ├── EventClient.swift │ │ └── Mocks.swift │ ├── EventClientLive │ │ └── Live.swift │ ├── EventFormView │ │ ├── EventFormAction.swift │ │ ├── EventFormEnvironment.swift │ │ ├── EventFormReducer.swift │ │ ├── EventFormState.swift │ │ └── EventFormView.swift │ ├── EventView │ │ ├── EventRowView.swift │ │ ├── EventView.swift │ │ ├── EventsAction.swift │ │ ├── EventsEnvironment.swift │ │ ├── EventsReducer.swift │ │ ├── EventsState.swift │ │ └── EventsStateMocks.swift │ ├── HangoutDetailsFeature │ │ ├── EventDetailsAction.swift │ │ ├── EventDetailsOverlayReducer.swift │ │ ├── EventDetailsOverlayView.swift │ │ ├── EventDetailsReducer.swift │ │ ├── EventDetailsState.swift │ │ ├── EventDetailsView.swift │ │ └── Resources │ │ │ ├── Media.xcassets │ │ │ ├── Contents.json │ │ │ └── hangout_dt.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── hangout_dt.jpg │ │ │ ├── hangout_bc.png │ │ │ └── hangout_dt.jpg │ ├── ImagePicker │ │ └── ImagePicker.swift │ ├── LocationReducer │ │ └── LocationReducer.swift │ ├── LocationSearchClient │ │ ├── Client.swift │ │ ├── Live.swift │ │ ├── Mock.swift │ │ └── Models.swift │ ├── Logger │ │ └── Extension+Logger.swift │ ├── MapView │ │ ├── DynamicHeightForTextFieldInSwiftUI.swift │ │ ├── LocationEnvironment.swift │ │ ├── LocationSearch.swift │ │ ├── LocationSearchAction.swift │ │ ├── LocationSearchManager.swift │ │ ├── LocationSearchState.swift │ │ ├── MapView.swift │ │ └── SearchMapView.swift │ ├── MyEventsView │ │ ├── EventRowView.swift │ │ ├── MyEventsAction.swift │ │ ├── MyEventsListView.swift │ │ └── MyEventsReducer.swift │ ├── ProfileView │ │ ├── File.swift │ │ ├── ProfileAction.swift │ │ ├── ProfileReducer.swift │ │ ├── ProfileState.swift │ │ └── ProfileView.swift │ ├── RegisterFormFeature │ │ ├── IDFAPermissionView.swift │ │ ├── LocationPermissionView.swift │ │ ├── NotificationPermissionView.swift │ │ ├── RegisterFormReducer.swift │ │ └── RegisterFormView.swift │ ├── SettingsFeature │ │ ├── DistanceFilterView.swift │ │ ├── NotificationsSettingsView.swift │ │ ├── Settings.swift │ │ ├── SettingsNavigationLink.swift │ │ ├── SettingsRow.swift │ │ ├── SettingsView.swift │ │ ├── TermsAndPrivacyWebView.swift │ │ ├── TermsWebView.swift │ │ └── UserSettings.swift │ ├── TabsView │ │ ├── TabState.swift │ │ ├── TabsAction.swift │ │ ├── TabsEnvironment.swift │ │ ├── TabsReducer.swift │ │ └── TabsView.swift │ ├── UserClient │ │ ├── Mocks.swift │ │ └── UserClient.swift │ ├── UserClientLive │ │ └── Live.swift │ ├── WebSocketClient │ │ └── WebSocketClient.swift │ └── WebSocketReducer │ │ └── WebSocketReducer.swift └── Tests │ ├── AppFeatureTests │ └── AppFeatureTests.swift │ ├── AuthenticationViewTests │ └── AuthenticationViewTests.swift │ ├── ContactsViewTests │ └── ContactsViewTests.swift │ ├── EventFormViewTests │ └── EventFormViewTests.swift │ ├── EventViewTests │ └── EventsViewTests.swift │ └── MapViewTests │ └── MapViewTests.swift ├── Gemfile ├── Gemfile.lock ├── README.md └── fastlane ├── Appfile.swift ├── Fastfile.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 └── swift ├── Actions.swift ├── Appfile.swift ├── ArgumentProcessor.swift ├── ControlCommand.swift ├── Deliverfile.swift ├── DeliverfileProtocol.swift ├── Fastfile.swift ├── Fastlane.swift ├── FastlaneSwiftRunner ├── FastlaneSwiftRunner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── FastlaneRunner.xcscheme └── README.txt ├── Gymfile.swift ├── GymfileProtocol.swift ├── LaneFileProtocol.swift ├── MainProcess.swift ├── Matchfile.swift ├── MatchfileProtocol.swift ├── OptionalConfigValue.swift ├── Plugins.swift ├── Precheckfile.swift ├── PrecheckfileProtocol.swift ├── RubyCommand.swift ├── RubyCommandable.swift ├── Runner.swift ├── RunnerArgument.swift ├── Scanfile.swift ├── ScanfileProtocol.swift ├── Screengrabfile.swift ├── ScreengrabfileProtocol.swift ├── Snapshotfile.swift ├── SnapshotfileProtocol.swift ├── SocketClient.swift ├── SocketClientDelegateProtocol.swift ├── SocketResponse.swift ├── formatting ├── Brewfile ├── Brewfile.lock.json └── Rakefile ├── main.swift └── upgrade_manifest.json /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | pull_request: 5 | branches: [ main, develop ] 6 | 7 | jobs: 8 | 9 | swiftLint: 10 | runs-on: macos-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install Bundle 14 | run: bundle install 15 | - name: Run swiftlint 16 | run: bundle exec fastlane swiftLintLane 17 | 18 | build: 19 | needs: swiftLint 20 | env: 21 | DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer 22 | runs-on: macos-11.0 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Install Bundle 26 | run: bundle install 27 | - name: Build 28 | run: bundle exec fastlane buildLane 29 | - name: Upload build 30 | uses: actions/upload-artifact@v2 31 | with: 32 | name: build 33 | path: '"derivedData/Build/Products/Debug CI-iphonesimulator/Addame.app"' 34 | 35 | unitTests: 36 | needs: build 37 | env: 38 | DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer 39 | runs-on: macos-11.0 40 | steps: 41 | - uses: actions/checkout@v2 42 | - name: Install Bundle 43 | run: bundle install 44 | - name: Run unit tests 45 | run: bundle exec fastlane unitTestLane 46 | - name: Run tests 47 | uses: actions/download-artifact@v2 48 | with: 49 | name: unitTests 50 | path: '"derivedData/Build/Products/Debug CI-iphonesimulator/EventFormViewTests.xctest"' 51 | 52 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "blankLineBetweenMembers" : { 3 | "ignoreSingleLineProperties" : true 4 | }, 5 | "indentation" : { 6 | "spaces" : 2 7 | }, 8 | "indentConditionalCompilationBlocks" : true, 9 | "lineBreakBeforeControlFlowKeywords" : false, 10 | "lineBreakBeforeEachArgument" : false, 11 | "lineLength" : 200, 12 | "maximumBlankLines" : 1, 13 | "respectsExistingLineBreaks" : true, 14 | "rules" : { 15 | "AllPublicDeclarationsHaveDocumentation" : false, 16 | "AlwaysUseLowerCamelCase" : true, 17 | "AmbiguousTrailingClosureOverload" : true, 18 | "BeginDocumentationCommentWithOneLineSummary" : true, 19 | "BlankLineBetweenMembers" : true, 20 | "CaseIndentLevelEqualsSwitch" : true, 21 | "DoNotUseSemicolons" : true, 22 | "DontRepeatTypeInStaticProperties" : true, 23 | "FullyIndirectEnum" : true, 24 | "GroupNumericLiterals" : true, 25 | "IdentifiersMustBeASCII" : true, 26 | "MultiLineTrailingCommas" : true, 27 | "NeverForceUnwrap" : true, 28 | "NeverUseForceTry" : true, 29 | "NeverUseImplicitlyUnwrappedOptionals" : false, 30 | "NoAccessLevelOnExtensionDeclaration" : true, 31 | "NoBlockComments" : true, 32 | "NoCasesWithOnlyFallthrough" : true, 33 | "NoEmptyTrailingClosureParentheses" : true, 34 | "NoLabelsInCasePatterns" : true, 35 | "NoLeadingUnderscores" : true, 36 | "NoParensAroundConditions" : true, 37 | "NoVoidReturnOnFunctionSignature" : true, 38 | "OneCasePerLine" : true, 39 | "OneVariableDeclarationPerLine" : true, 40 | "OnlyOneTrailingClosureArgument" : true, 41 | "OrderedImports" : true, 42 | "ReturnVoidInsteadOfEmptyTuple" : true, 43 | "UseEnumForNamespacing" : true, 44 | "UseLetInEveryBoundCaseVariable" : false, 45 | "UseShorthandTypeNames" : true, 46 | "UseSingleLinePropertyGetter" : true, 47 | "UseSynthesizedInitializer" : true, 48 | "UseTripleSlashForDocumentationComments" : true, 49 | "ValidateDocumentationComments" : true 50 | }, 51 | "tabWidth" : 8, 52 | "version" : 1 53 | } 54 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - empty_enum_arguments 3 | - vertical_parameter_alignment_on_call 4 | - weak_delegate 5 | - vertical_parameter_alignment 6 | - closure_parameter_position 7 | - todo 8 | - multiple_closures_with_trailing_closure 9 | opt_in_rules: 10 | - empty_count 11 | - explicit_init 12 | - closure_spacing 13 | - overridden_super_call 14 | - redundant_nil_coalescing 15 | - private_outlet 16 | - nimble_operator 17 | - operator_usage_whitespace 18 | - closure_end_indentation 19 | - first_where 20 | - prohibited_super_call 21 | - fatal_error_message 22 | - unneeded_parentheses_in_closure_argument 23 | - pattern_matching_keywords 24 | - array_init 25 | - literal_expression_end_indentation 26 | - joined_default_parameter 27 | - contains_over_first_not_nil 28 | - override_in_extension 29 | - private_action 30 | - quick_discouraged_call 31 | - quick_discouraged_focused_test 32 | - quick_discouraged_pending_test 33 | - single_test_class 34 | - sorted_first_last 35 | 36 | analyzer_rules: # Rules run by `swiftlint analyze` (experimental) 37 | - explicit_self 38 | 39 | warning_threshold: 1 40 | 41 | line_length: 120 42 | 43 | type_body_length: 44 | - 300 # warning 45 | - 300 # error 46 | 47 | file_length: 48 | warning: 400 # warning 49 | error: 400 # error 50 | 51 | identifier_name: 52 | min_length: 53 | error: 2 54 | max_length: 55 | error: 50 56 | excluded: 57 | - id 58 | - _id 59 | 60 | indentation: 2 61 | 62 | excluded: 63 | - derivedData 64 | - test_output 65 | - fastlane 66 | 67 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji) 68 | -------------------------------------------------------------------------------- /Addame.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Addame.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Addame/Addame.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /Addame/Addame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TComposableAAddaMeApp.swift 3 | // TComposableAAddaMe 4 | // 5 | // Created by Saroar Khandoker on 05.04.2021. 6 | // 7 | 8 | import AppFeature 9 | import ComposableArchitecture 10 | import SwiftUI 11 | import UIKit 12 | 13 | final class AppDelegate: NSObject, UIApplicationDelegate { 14 | let store = Store( 15 | initialState: AppReducer.State() 16 | ) { 17 | AppReducer() 18 | ._printChanges() 19 | } 20 | 21 | 22 | func application( 23 | _ application: UIApplication, 24 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 25 | ) -> Bool { 26 | self.store.send(.appDelegate(.didFinishLaunching)) 27 | return true 28 | } 29 | 30 | func application( 31 | _ application: UIApplication, 32 | didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data 33 | ) { 34 | self.store.send(.appDelegate(.didRegisterForRemoteNotifications(.success(deviceToken)))) 35 | } 36 | 37 | func application( 38 | _ application: UIApplication, 39 | didFailToRegisterForRemoteNotificationsWithError error: Error 40 | ) { 41 | self.store.send(.appDelegate(.didRegisterForRemoteNotifications(.failure(error)))) 42 | } 43 | } 44 | 45 | @main 46 | struct AddameApp: App { 47 | 48 | @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate 49 | @Environment(\.scenePhase) private var scenePhase 50 | 51 | var body: some Scene { 52 | WindowGroup { 53 | AppView(store: self.appDelegate.store) 54 | } 55 | .onChange(of: self.scenePhase) { 56 | self.appDelegate.store.send(.didChangeScenePhase($0)) 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /Addame/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 | -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/Addame/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /Addame/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Addame/Configs/DevelopmentCI.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:/$()/localhost:8080/v1 13 | WEB_SOCKET_URL = ws:/$()/localhost:6060/v1/chat 14 | 15 | // Image Uploading to DigitalOcen 16 | ACCESS_KEY_ID = value 17 | SECRET_ACCESS_KEY = value 18 | 19 | -------------------------------------------------------------------------------- /Addame/Configs/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 | 13 | private enum Keys { 14 | // swiftlint:disable nesting 15 | enum Plist { 16 | static let rootURL = "ROOT_URL" 17 | static let webSocketURL = "WEB_SOCKET_URL" 18 | static let accessKeyId = "ACCESS_KEY_ID" 19 | static let secretAccessKey = "SECRET_ACCESS_KEY" 20 | } 21 | } 22 | 23 | // MARK: - Plist 24 | 25 | private static let infoDictionary: [String: Any] = { 26 | guard let dict = Bundle.main.infoDictionary else { 27 | fatalError("Plist file not found") 28 | } 29 | 30 | return dict 31 | }() 32 | 33 | // MARK: - Plist values 34 | 35 | static let rootURL: URL = { 36 | guard let rootURLstring = EnvironmentKeys.infoDictionary["ROOT_URL"] as? String else { 37 | fatalError("Root URL not set in plist for this environment") 38 | } 39 | 40 | guard let url = URL(string: rootURLstring) else { 41 | fatalError("Root URL is invalid") 42 | } 43 | 44 | return url 45 | }() 46 | 47 | static let webSocketURL: URL = { 48 | guard let webSocketString = EnvironmentKeys.infoDictionary[Keys.Plist.webSocketURL] as? String 49 | else { 50 | fatalError("WEB SOCKET URL Key not set in plist for this environment") 51 | } 52 | 53 | guard let url = URL(string: webSocketString) else { 54 | fatalError("WEB SOCKET URL is invalid") 55 | } 56 | 57 | return url 58 | }() 59 | 60 | static let accessKeyId: String = { 61 | guard let accessKeyId = EnvironmentKeys.infoDictionary["ACCESS_KEY_ID"] as? String else { 62 | fatalError("ACCESS_KEY_ID not set in plist for this environment") 63 | } 64 | 65 | return accessKeyId 66 | }() 67 | 68 | static let secretAccessKey: String = { 69 | guard let secretAccessKey = EnvironmentKeys.infoDictionary["SECRET_ACCESS_KEY"] as? String 70 | else { 71 | fatalError("SECRET_ACCESS_KEY not set in plist for this environment") 72 | } 73 | 74 | return secretAccessKey 75 | }() 76 | } 77 | -------------------------------------------------------------------------------- /Addame/Configs/ProductionCI.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Production.xcconfig 3 | // AddaMeIOS 4 | // 5 | // Created by Saroar Khandoker on 11.11.2020. 6 | // 7 | 8 | // Configuration settings file format documentation can be found at: 9 | // https://help.apple.com/xcode/#/dev745c5c974 10 | 11 | ROOT_URL = https:/$()/domain/v1 12 | WEB_SOCKET_URL = ws:/$()/domain:6060/v1/chat 13 | 14 | // Image Uploading to DigitalOcen 15 | ACCESS_KEY_ID = value 16 | SECRET_ACCESS_KEY = value 17 | -------------------------------------------------------------------------------- /Addame/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AddameSPM/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/AppFeature.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 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/AppFeatureTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/AttachmentClient.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/AuthenticationView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/ChatView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/ContactClient.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/ContactClientLive.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/ContactsViewTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/Event.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/EventClient.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/EventClientLive.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/EventFormView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/EventFormViewTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/EventView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/EventViewTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/KeychainService.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/PathMonitorClient.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/ProfileView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/SettingsView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/SharedModels.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/SwiftUIExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/SwiftUIHelpers.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/.swiftpm/xcode/xcshareddata/xcschemes/TabsView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /AddameSPM/README.md: -------------------------------------------------------------------------------- 1 | # Addame 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /AddameSPM/Sources/APIClient/APIClient.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | import URLRouting 4 | import InfoPlist 5 | import FoundationExtension 6 | import Dependencies 7 | import AppConfiguration 8 | import KeychainClient 9 | import Build 10 | import AddaSharedModels 11 | 12 | public typealias APIClient = URLRoutingClient 13 | 14 | public enum APIClientKey: TestDependencyKey { 15 | public static let testValue = APIClient.failing 16 | } 17 | 18 | extension APIClientKey: DependencyKey { 19 | public static let baseURL = DependencyValues._current.appConfiguration.apiURL 20 | public static let liveValue: APIClient = APIClient.live( 21 | router: siteRouter.baseURL(APIClientKey.baseURL) 22 | ) 23 | } 24 | 25 | extension DependencyValues { 26 | public var apiClient: APIClient { 27 | get { self[APIClientKey.self] } 28 | set { self[APIClientKey.self] = newValue } 29 | } 30 | } 31 | 32 | public enum APIError: Error { 33 | case serviceError(statusCode: Int, APIErrorPayload) 34 | case unknown 35 | 36 | init(error: Error) { 37 | if let apiError = error as? APIError { 38 | self = apiError 39 | } else { 40 | self = .unknown 41 | } 42 | } 43 | } 44 | 45 | extension APIError: Equatable { 46 | 47 | } 48 | 49 | public struct APIErrorPayload: Codable, Equatable { 50 | let reason: String? 51 | } 52 | 53 | extension APIClient { 54 | 55 | @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) 56 | public func request( 57 | for route: Route, 58 | as type: Value.Type = Value.self, 59 | decoder: JSONDecoder = .init() 60 | ) async throws -> Value { 61 | guard var request = try? siteRouter.baseURL(APIClientKey.baseURL).request(for: route) 62 | else { throw URLError(.badURL) } 63 | request.setHeaders() 64 | 65 | let (data, response) = try await URLSession.shared.data(for: request) 66 | 67 | // #if DEBUG 68 | // #endif 69 | 70 | if let statusCode = (response as? HTTPURLResponse)?.statusCode { 71 | switch statusCode { 72 | case 200 ..< 300: 73 | return try decoder.decode(Value.self, from: data) 74 | 75 | case 400 ..< 500: 76 | let payload = try decoder.decode(APIErrorPayload.self, from: data) 77 | throw APIError.serviceError(statusCode: statusCode, payload) 78 | 79 | default: 80 | throw APIError.unknown 81 | } 82 | } else { 83 | throw APIError.unknown 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /AddameSPM/Sources/APIClient/URLRequest+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Dependencies 3 | import UIKit 4 | import AddaSharedModels 5 | 6 | extension URLRequest { 7 | 8 | public mutating func getToken() -> String? { 9 | let identifier = DependencyValues._current.build.identifier() 10 | let token = try? DependencyValues._current 11 | .keychainClient 12 | .readCodable(.token, identifier, RefreshTokenResponse.self) 13 | return token?.accessToken 14 | } 15 | 16 | mutating func setHeaders() { 17 | let token = getToken() ?? "" 18 | guard let infoDictionary = Bundle.main.infoDictionary else { return } 19 | 20 | let bundleName = infoDictionary[kCFBundleNameKey as String] ?? "NotifyWord" 21 | let marketingVersion = infoDictionary["CFBundleShortVersionString"].map { "/\($0)" } ?? "" 22 | let bundleVersion = infoDictionary[kCFBundleVersionKey as String].map { " bundle/\($0)" } ?? "" 23 | let gitSha = (infoDictionary["GitSHA"] as? String).map { $0.isEmpty ? "" : "git/\($0)" } ?? "" 24 | let identifierForVendor = UIDevice.current.identifierForVendor?.uuidString ?? "" 25 | self.setValue( 26 | "\(bundleName)\(marketingVersion)\(bundleVersion)\(gitSha)", 27 | forHTTPHeaderField: "User-Agent" 28 | ) 29 | self.setValue("\(identifierForVendor)", forHTTPHeaderField: "IdentifierForVendor") 30 | self.addValue("Bearer " + token, forHTTPHeaderField: "Authorization") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AddameSPM/Sources/APIClient/UserClient.swift: -------------------------------------------------------------------------------- 1 | import AddaSharedModels 2 | import Dependencies 3 | 4 | public struct UserClient { 5 | public typealias UserMeHandler = @Sendable (String) async throws -> UserOutput 6 | public typealias UserUpdateHandler = @Sendable (UserOutput) async throws -> UserOutput 7 | public typealias UserDeleteHandler = @Sendable (String) async throws -> Bool 8 | 9 | public let userMeHandler: UserMeHandler 10 | public let update: UserUpdateHandler 11 | public let delete: UserDeleteHandler 12 | 13 | public init( 14 | userMeHandler: @escaping UserMeHandler, 15 | update: @escaping UserUpdateHandler, 16 | delete: @escaping UserDeleteHandler 17 | ) { 18 | self.userMeHandler = userMeHandler 19 | self.update = update 20 | self.delete = delete 21 | } 22 | } 23 | 24 | extension UserClient { 25 | 26 | public static var live: UserClient = 27 | .init( 28 | userMeHandler: { id in 29 | @Dependency(\.apiClient) var apiClient 30 | return try await apiClient.request( 31 | for: .authEngine(.users(.user(id: id, route: .find))), 32 | as: UserOutput.self, 33 | decoder: .iso8601 34 | ) 35 | }, 36 | update: { userInput in 37 | @Dependency(\.apiClient) var apiClient 38 | return try await apiClient.request( 39 | for: .authEngine(.users(.update(input: userInput))), 40 | as: UserOutput.self, 41 | decoder: .iso8601 42 | ) 43 | }, 44 | 45 | delete: { id in 46 | @Dependency(\.apiClient) var apiClient 47 | return try await apiClient.data( 48 | for: .authEngine(.users(.user(id: id, route: .delete))) 49 | ).response.isResponseOK() 50 | } 51 | ) 52 | } 53 | 54 | import Foundation 55 | extension URLResponse { 56 | func isResponseOK() -> Bool { 57 | if let httpResponse = self as? HTTPURLResponse { 58 | return (200...299).contains(httpResponse.statusCode) 59 | } 60 | return false 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AsyncImageLoder/AssetExtractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetExtractor.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 26.01.2021. 6 | // 7 | 8 | import Foundation 9 | 10 | #if os(macOS) 11 | 12 | #else 13 | import UIKit 14 | #endif 15 | 16 | public enum AssetExtractor { 17 | public static func createLocalUrl(forImageNamed name: String) -> URL? { 18 | let fileManager = FileManager.default 19 | let cacheDirectory = fileManager.urls( 20 | for: .cachesDirectory, in: .userDomainMask 21 | )[0] 22 | 23 | let url = cacheDirectory.appendingPathComponent("\(name).png") 24 | 25 | guard fileManager.fileExists(atPath: url.path) else { 26 | guard 27 | let image = UIImage(named: name), 28 | let data = image.pngData() 29 | else { 30 | print(#line, self, "cant find image by name: \(name)") 31 | return nil 32 | } 33 | 34 | fileManager.createFile(atPath: url.path, contents: data, attributes: nil) 35 | return url 36 | } 37 | 38 | return url 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AsyncImageLoder/AsyncImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncImage.swift 3 | // AddaMeIOS 4 | // 5 | // Created by Saroar Khandoker on 31.08.2020. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct AsyncImage: View { 11 | @StateObject private var loader: ImageLoader 12 | private var placeholder: Placeholder 13 | private var image: (UIImage) -> Image 14 | 15 | public init( 16 | url: URL, 17 | @ViewBuilder placeholder: () -> Placeholder, 18 | @ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:) 19 | ) { 20 | self.placeholder = placeholder() 21 | self.image = image 22 | _loader = StateObject( 23 | wrappedValue: ImageLoader( 24 | url: url, 25 | cache: Environment(\.imageCache).wrappedValue 26 | ) 27 | ) 28 | } 29 | 30 | public var body: some View { 31 | content 32 | .onAppear(perform: loader.load) 33 | } 34 | 35 | private var content: some View { 36 | Group { 37 | if let loaderImage = loader.image { 38 | image(loaderImage) 39 | } else { 40 | placeholder 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AsyncImageLoder/AsyncImageLoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.swift 3 | // AddaMeIOS 4 | // 5 | // Created by Saroar Khandoker on 30.08.2020. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | import SwiftUI 11 | 12 | public class ImageLoader: ObservableObject { 13 | @Published var image: UIImage? 14 | private let url: URL 15 | private var cancellable: AnyCancellable? 16 | private var cache: ImageCache? 17 | private(set) var isLodaing = false 18 | private static let imageProcessingQueue = DispatchQueue(label: "image-processing") 19 | 20 | init(url: URL, cache: ImageCache? = nil) { 21 | self.url = url 22 | self.cache = cache 23 | } 24 | 25 | deinit { 26 | cancel() 27 | } 28 | 29 | func load() { 30 | guard !isLodaing else { return } 31 | 32 | if let image = cache?[url] { 33 | self.image = image 34 | return 35 | } 36 | 37 | cancellable = URLSession.shared.dataTaskPublisher(for: url) 38 | .map { UIImage(data: $0.data) } 39 | .replaceError(with: nil) 40 | .handleEvents( 41 | receiveSubscription: { _ in self.onStart() }, 42 | receiveOutput: { self.cache($0) }, 43 | receiveCompletion: { _ in self.onFinished() }, 44 | receiveCancel: { self.onFinished() } 45 | ) 46 | .subscribe(on: Self.imageProcessingQueue) 47 | .receive(on: DispatchQueue.main) 48 | .delay(for: 6, scheduler: DispatchQueue.main) 49 | .sink(receiveValue: { [weak self] result in 50 | self?.image = result 51 | }) 52 | } 53 | 54 | func cancel() { 55 | cancellable?.cancel() 56 | } 57 | 58 | private func onStart() { 59 | isLodaing = true 60 | } 61 | 62 | private func onFinished() { 63 | isLodaing = false 64 | } 65 | 66 | private func cache(_ image: UIImage?) { 67 | image.map { cache?[url] = $0 } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AsyncImageLoder/EnvironmentValues.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 | public struct ImageCacheKey: EnvironmentKey { 11 | public static let defaultValue: ImageCache = TemporaryImageCache() 12 | } 13 | 14 | extension EnvironmentValues { 15 | public var imageCache: ImageCache { 16 | get { self[ImageCacheKey.self] } 17 | set { self[ImageCacheKey.self] = newValue } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AsyncImageLoder/ImageDraw.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageDraw.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 28.01.2021. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | public class ImageDraw { 12 | private let renderer = UIGraphicsImageRenderer(size: CGSize(width: 400, height: 400)) 13 | private var colors = [ 14 | UIColor.red, UIColor.brown, UIColor.yellow, UIColor.green, UIColor.black, UIColor.blue 15 | ] 16 | 17 | public init() {} 18 | 19 | public func buildRandomImage() -> UIImage { 20 | colors.shuffle() 21 | 22 | let image = renderer.image { context in 23 | UIColor.darkGray.setStroke() 24 | context.stroke(renderer.format.bounds) 25 | 26 | let count = 400 / colors.count 27 | 28 | colors.enumerated().forEach { idx, color in 29 | color.setFill() 30 | context.fill(CGRect(x: idx * count, y: 0, width: idx * count, height: 400)) 31 | } 32 | } 33 | 34 | return image 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AsyncImageLoder/ImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePicker.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 26.01.2021. 6 | // 7 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AttachmentS3Client/AwsS3Manager.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AttachmentS3Client/Mocks.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // swiftlint:disable line_length superfluous_disable_command 4 | extension AttachmentS3Client { 5 | public static let empty = Self( 6 | uploadImageToS3: { _, _, _ in "" } 7 | ) 8 | 9 | public static let happyPath = Self( 10 | uploadImageToS3: { _, _, _ in "https://adda.nyc3.digitaloceanspaces.com/uploads/images/5fabb05d2470c17919b3c0e2/5fabb05d2470c17919b3c0e2_1605792619988.jpeg" 11 | } 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AuthenticationView/PhoneNumberKit+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhoneNumberKit+Extension.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.10.2021. 6 | // 7 | 8 | import Foundation 9 | import PhoneNumberKit 10 | 11 | // extension PhoneNumberKit { 12 | // 13 | // public override convenience init() { 14 | // self.init(metadataCallback: PhoneNumberKit.bundleMetadataCallback) 15 | // } 16 | // 17 | // public static func bundleMetadataCallback() throws -> Data? { 18 | // guard let jsonPath = Bundle.main.path(forResource: "PhoneNumberMetadata", ofType: "json") else { 19 | // throw PhoneNumberError.metadataNotFound 20 | // } 21 | // let data = try Data(contentsOf: URL(fileURLWithPath: jsonPath)) 22 | // return data 23 | // } 24 | // 25 | // } 26 | -------------------------------------------------------------------------------- /AddameSPM/Sources/AuthenticationView/PhoneNumberTextFieldView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhoneNumberTextFieldView.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 15.09.2021. 6 | // 7 | 8 | import PhoneNumberKit 9 | import SwiftUI 10 | 11 | public struct PhoneNumberTextFieldView: UIViewRepresentable, Equatable { 12 | public static func == (lhs: PhoneNumberTextFieldView, rhs: PhoneNumberTextFieldView) -> Bool { 13 | return lhs.isValid == rhs.isValid && lhs.phoneNumber == rhs.phoneNumber 14 | } 15 | 16 | public func makeCoordinator() -> Coordinator { 17 | Coordinator(self) 18 | } 19 | 20 | @Binding var phoneNumber: String 21 | @Binding var isValid: Bool 22 | 23 | let phoneTextField = PhoneNumberTextField() 24 | 25 | public func makeUIView(context: Context) -> PhoneNumberTextField { 26 | phoneTextField.withExamplePlaceholder = true 27 | phoneTextField.withFlag = true 28 | phoneTextField.withPrefix = true 29 | phoneTextField.withExamplePlaceholder = true 30 | // phoneTextField.placeholder = "Enter phone number" 31 | phoneTextField.becomeFirstResponder() 32 | phoneTextField.addTarget( 33 | context.coordinator, action: #selector(Coordinator.onTextUpdate), for: .editingChanged) 34 | return phoneTextField 35 | } 36 | 37 | public func getCurrentText() { 38 | guard let phoneText = phoneTextField.text else { 39 | return 40 | } 41 | phoneNumber = phoneText 42 | } 43 | 44 | public func updateUIView(_: PhoneNumberTextField, context _: Context) {} 45 | 46 | public class Coordinator: NSObject, UITextFieldDelegate { 47 | var control: PhoneNumberTextFieldView 48 | 49 | init(_ control: PhoneNumberTextFieldView) { 50 | self.control = control 51 | } 52 | 53 | @objc func onTextUpdate(textField _: UITextField) { 54 | control.isValid = control.phoneTextField.isValidNumber 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ChatClient/ChatClient.swift: -------------------------------------------------------------------------------- 1 | // import Combine 2 | // import Foundation 3 | // import FoundationExtension 4 | // import AddaSharedModels 5 | // import URLRouting 6 | // import InfoPlist 7 | // 8 | // public struct ChatClient { 9 | // // swiftlint:disable superfluous_disable_command 10 | // public static let apiClient: URLRoutingClient = .live( 11 | // 12 | // router: siteRouter.baseRequestData( 13 | // .init( 14 | // scheme: EnvironmentKeys.rootURL.scheme, 15 | // host: EnvironmentKeys.rootURL.host, 16 | // port: EnvironmentKeys.setPort(), 17 | // headers: ["Authorization": [ "Bearer "]] 18 | // ) 19 | // ) 20 | // ) 21 | // 22 | // public typealias MessageListHandler = @Sendable (QueryItem, String) async throws -> MessagePage 23 | // 24 | // public let messages: MessageListHandler 25 | // 26 | // public init( 27 | // messages: @escaping MessageListHandler 28 | // ) { 29 | // self.messages = messages 30 | // } 31 | // } 32 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ChatClient/Mocks.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// Mocks.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 05.03.2021. 6 | //// 7 | // 8 | // import Combine 9 | // import Foundation 10 | // import FoundationExtension 11 | // 12 | // import AddaSharedModels 13 | // import BSON 14 | // 15 | // extension ChatClient { 16 | // public static let happyPath = Self( 17 | // messages: { _, _ in MessagePage.draff } 18 | // ) 19 | // } 20 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ChatClientLive/Live.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Live.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 05.03.2021. 6 | // 7 | 8 | // 9 | // import Combine 10 | // import Foundation 11 | // import InfoPlist 12 | // import AddaSharedModels 13 | // import URLRouting 14 | // 15 | // extension ChatClient { 16 | // public static var live: ChatClient = .init( 17 | // messages: { query, conversationId in 18 | // return try await ChatClient.apiClient.decodedResponse( 19 | // for: .chatEngine( 20 | // .conversations( 21 | // .conversation(id: conversationId, route: .messages(.list(query: query))) 22 | // ) 23 | // ), 24 | // as: MessagePage.self, 25 | // decoder: .iso8601 26 | // ).value 27 | // } 28 | // ) 29 | // 30 | // } 31 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ChatView/ChatAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.04.2021. 6 | // 7 | 8 | import Foundation 9 | 10 | import AddaSharedModels 11 | 12 | 13 | 14 | extension Chat.Action { 15 | // swiftlint:disable cyclomatic_complexity 16 | public static func view(_ localAction: ChatView.ViewAction) -> Self { 17 | switch localAction { 18 | case .onAppear: 19 | return .onAppear 20 | case .alertDismissed: 21 | return .alertDismissed 22 | case let .fetchMoreMessageIfNeeded(currentItem: currentItem): 23 | return .fetchMoreMessageIfNeeded(currentItem: currentItem) 24 | case let .fetchMoreMessage(currentItem: item): 25 | return .fetchMoreMessage(currentItem: item) 26 | case let .message(index, action): 27 | return .message(index: index, action: action) 28 | case .messagesResponse(let response): 29 | return .messagesResponse(response) 30 | case .webSocketReducer(let wsra): 31 | return .webSocketReducer(wsra) 32 | case .chatButtom(let cb): 33 | return .chatButtom(cb) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ChatView/ChatEnvironment.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// ChatEnvironment.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 19.04.2021. 6 | //// 7 | //import Combine 8 | //import ComposableArchitecture 9 | // 10 | //import KeychainClient 11 | //import AddaSharedModels 12 | 13 | //import Foundation 14 | // 15 | //public struct ChatEnvironment { 16 | // public let websocketClient: WebSocketClient 17 | // public var mainQueue: AnySchedulerOf 18 | // public var backgroundQueue: AnySchedulerOf 19 | // 20 | // public init( 21 | // websocketClient: WebSocketClient, 22 | // mainQueue: AnySchedulerOf, 23 | // backgroundQueue: AnySchedulerOf 24 | // ) { 25 | // self.websocketClient = websocketClient 26 | // self.mainQueue = mainQueue 27 | // self.backgroundQueue = backgroundQueue 28 | // } 29 | // 30 | // public var currentUser: UserOutput { 31 | // return UserOutput.withFirstName 32 | // } 33 | //} 34 | // 35 | //extension ChatEnvironment { 36 | // public static let live: ChatEnvironment = .init( 37 | // websocketClient: .live, 38 | // mainQueue: .main, 39 | // backgroundQueue: .main 40 | // ) 41 | //} 42 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ChatView/ChatListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatListView.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 17.06.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import AddaSharedModels 10 | import SwiftUI 11 | 12 | struct ChatListView: View { 13 | let store: StoreOf 14 | 15 | var body: some View { 16 | WithViewStore(self.store, observe: { $0 }) { viewStore in 17 | ForEachStore( 18 | self.store.scope( 19 | state: \.messages, 20 | action: Chat.Action.message(index:action:) 21 | ) 22 | ) { chatStore in 23 | WithViewStore(chatStore, observe: { $0 }) { messageViewStore in 24 | ChatRowView(store: chatStore) 25 | .onAppear { 26 | viewStore.send(.fetchMoreMessageIfNeeded(currentItem: messageViewStore.state)) 27 | } 28 | .scaleEffect(x: 1, y: -1, anchor: .center) 29 | // .listRowSeparatorHiddenIfAvaibale() 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ChatView/ChatState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatState.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.04.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import AddaSharedModels 10 | import WebSocketReducer 11 | 12 | extension Chat.State { 13 | public static let placeholderMessages = Self( 14 | isLoadingPage: true, 15 | conversation: .lookingForAcompanyDraff, 16 | messages: .init(uniqueElements: MessagePage.draff.items), 17 | currentUser: .withFirstName, 18 | websocketState: .init(user: .withFirstName) 19 | ) 20 | 21 | // ( 22 | // currentUser 23 | // isLoadingPage: true, 24 | // messages: .init(uniqueElements: MessagePage.draff.items) 25 | // ) 26 | } 27 | -------------------------------------------------------------------------------- /AddameSPM/Sources/CombineHelpers/Combine.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | extension Publisher where Output == Never { 4 | public func setOutputType(to _: NewOutput.Type) -> AnyPublisher { 5 | func absurd(_: Never) -> A {} 6 | return map(absurd).eraseToAnyPublisher() 7 | } 8 | } 9 | 10 | extension Publisher { 11 | public func ignoreOutput( 12 | setOutputType _: NewOutput.Type 13 | ) -> AnyPublisher { 14 | return 15 | ignoreOutput() 16 | .setOutputType(to: NewOutput.self) 17 | } 18 | 19 | public func ignoreFailure( 20 | setFailureType _: NewFailure.Type 21 | ) -> AnyPublisher { 22 | self 23 | .catch { _ in Empty() } 24 | .setFailureType(to: NewFailure.self) 25 | .eraseToAnyPublisher() 26 | } 27 | 28 | public func ignoreFailure() -> AnyPublisher { 29 | self 30 | .catch { _ in Empty() } 31 | .setFailureType(to: Never.self) 32 | .eraseToAnyPublisher() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ComposableArchitectureHelpers/WebSocketId.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebSocketId.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 28.06.2021. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct WebSocketId: Hashable { 11 | public init() {} 12 | } 13 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactClient/Mocks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocks.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 11.03.2021. 6 | // 7 | 8 | import Combine 9 | import CombineContacts 10 | import Contacts 11 | import Foundation 12 | 13 | import AddaSharedModels 14 | 15 | extension ContactClient { 16 | public static let notDetermined = Self( 17 | authorization: { 18 | CNAuthorizationStatus.notDetermined 19 | }, 20 | buidContacts: { 21 | MobileNumbersInput(mobileNumber: [""]) 22 | } 23 | ) 24 | 25 | public static let restricted = Self( 26 | authorization: { CNAuthorizationStatus.restricted }, 27 | buidContacts: { MobileNumbersInput(mobileNumber: [""]) } 28 | ) 29 | 30 | public static let denied = Self( 31 | authorization: { CNAuthorizationStatus.denied }, 32 | buidContacts: { MobileNumbersInput(mobileNumber: [""]) } 33 | ) 34 | 35 | public static let authorized = Self( 36 | authorization: { CNAuthorizationStatus.authorized }, 37 | buidContacts: { MobileNumbersInput(mobileNumber: ["+79218821217"]) } 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactClientLive/FlatMapLatest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlatMapLatest.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 05.04.2021. 6 | // 7 | 8 | import Combine 9 | 10 | extension Publisher { 11 | public func flatMapLatest( 12 | _ transformation: @escaping (Self.Output) -> U 13 | ) -> Publishers.FlatMapLatest 14 | where U: Publisher, U.Failure == Self.Failure { 15 | return Publishers.FlatMapLatest(upstream: self, transform: transformation) 16 | } 17 | } 18 | 19 | extension Publishers { 20 | public struct FlatMapLatest: Publisher 21 | where P: Publisher, Upstream: Publisher, P.Failure == Upstream.Failure { 22 | // swiftlint:disable:next nesting 23 | public typealias Output = P.Output 24 | public typealias Failure = Upstream.Failure 25 | // swiftlint:disable:previous nesting 26 | 27 | private let upstream: Upstream 28 | private let transform: (Upstream.Output) -> P 29 | 30 | init(upstream: Upstream, transform: @escaping (Upstream.Output) -> P) { 31 | self.upstream = upstream 32 | self.transform = transform 33 | } 34 | 35 | public func receive(subscriber: S) 36 | where S: Subscriber, P.Output == S.Input, Upstream.Failure == S.Failure { 37 | upstream.map(transform) 38 | .switchToLatest() 39 | .receive(subscriber: subscriber) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactClientLive/Live.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Live.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 11.03.2021. 6 | // 7 | 8 | import Combine 9 | import CombineContacts 10 | import Contacts 11 | import CoreData 12 | import CoreDataStore 13 | import Foundation 14 | import InfoPlist 15 | import PhoneNumberKit 16 | import AddaSharedModels 17 | import URLRouting 18 | import BSON 19 | 20 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactListAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactListAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 25.06.2021. 6 | // 7 | 8 | import ChatView 9 | import Contacts 10 | import AddaSharedModels 11 | import ComposableArchitecture 12 | 13 | // public enum ChatAction: Equatable {} 14 | 15 | public enum ContactListAction: Equatable { 16 | case onAppear 17 | case contactRow(id: String, action: ContactRow.Action) 18 | case contactsResponse(TaskResult<[ContactOutPut]>) 19 | } 20 | 21 | // extension ContactListAction { 22 | // static func view(_ localAction: ContactsView.ViewAction) -> Self { 23 | // switch localAction { 24 | // case .onAppear: 25 | // return .onAppear 26 | // case .alertDismissed: 27 | // return .alertDismissed 28 | // case .contactsAuthorizationStatus(let status): 29 | // return .contactsAuthorizationStatus(status) 30 | // case .contactsResponse(let response): 31 | // return .contactsResponse(response) 32 | // case let .contactRow(id: id, action: action): 33 | // return .contactRow(id: id, action: action) 34 | // } 35 | // } 36 | // } 37 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactListView.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 12.06.2021. 6 | // 7 | 8 | import AsyncImageLoder 9 | import ChatView 10 | import Combine 11 | import CombineContacts 12 | import ComposableArchitecture 13 | import ComposableArchitectureHelpers 14 | import Contacts 15 | import CoreData 16 | import CoreDataClient 17 | import CoreDataStore 18 | import Foundation 19 | 20 | import AddaSharedModels 21 | import SwiftUI 22 | import SwiftUIExtension 23 | 24 | struct ContactListView: View { 25 | @Environment(\.colorScheme) var colorScheme 26 | public let store: StoreOf 27 | 28 | public init(store: StoreOf) { 29 | self.store = store 30 | } 31 | 32 | public var body: some View { 33 | WithViewStore(self.store, observe: { $0 }) { _ in 34 | ForEachStore( 35 | self.store.scope( 36 | state: \.contacts, 37 | action: ContactsReducer.Action.contact(id:action:) 38 | ), 39 | content: ContactRowView.init(store:) 40 | ) 41 | } 42 | } 43 | } 44 | 45 | // struct ContactListView_Previews: PreviewProvider { 46 | // 47 | // static let environment = ContactsEnvironment( 48 | // coreDataClient: .init(contactClient: .authorized), 49 | // backgroundQueue: .immediate, 50 | // mainQueue: .immediate 51 | // ) 52 | // 53 | // static let store = Store( 54 | // initialState: ContactsState.contactsPlaceholder, 55 | // reducer: contactsReducer, 56 | // environment: environment 57 | // ) 58 | // 59 | // static var previews: some View { 60 | // ContactListView(store: store) 61 | // } 62 | // } 63 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactRowAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactRowAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 25.06.2021. 6 | // 7 | 8 | import ChatView 9 | import Contacts 10 | import AddaSharedModels 11 | 12 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactRowReducer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactRowReducer.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 25.06.2021. 6 | // 7 | 8 | import ChatView 9 | import Combine 10 | import ComposableArchitecture 11 | import ComposableArchitectureHelpers 12 | 13 | import AddaSharedModels 14 | import SwiftUI 15 | 16 | public struct ContactRowEnvironment {} 17 | 18 | public struct ContactRow: Reducer { 19 | public struct State: Equatable, Identifiable { 20 | public init( 21 | id: String? = UUID().uuidString, 22 | isMoving: Bool = false, 23 | contact: ContactOutPut 24 | ) { 25 | self.id = id 26 | self.isMoving = isMoving 27 | self.contact = contact 28 | } 29 | 30 | public var id: String? 31 | public var isMoving: Bool = false 32 | public var contact: ContactOutPut 33 | } 34 | 35 | public enum Action: Equatable { 36 | case moveToChatRoom(Bool) 37 | case chatWith(name: String, phoneNumber: String) 38 | } 39 | 40 | 41 | public init() {} 42 | 43 | public var body: some Reducer { 44 | 45 | Reduce(self.core) 46 | } 47 | 48 | func core(state: inout State, action: Action) -> Effect { 49 | switch action { 50 | 51 | case .moveToChatRoom: return .none 52 | case .chatWith: return .none 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactRowState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactRowState.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 26.06.2021. 6 | // 7 | 8 | import ChatView 9 | import ComposableArchitecture 10 | import Contacts 11 | 12 | import AddaSharedModels 13 | 14 | 15 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactsAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 12.05.2021. 6 | // 7 | 8 | 9 | 10 | // public enum ChatAction: Equatable {} 11 | 12 | 13 | 14 | // extension ContactsAction { 15 | // static func view(_ localAction: ContactsView.ViewAction) -> Self { 16 | // switch localAction { 17 | // case .onAppear: 18 | // return .onAppear 19 | // case .alertDismissed: 20 | // return .alertDismissed 21 | // case .moveChatRoom(let bool): 22 | // return .moveChatRoom(bool) 23 | // case .chat(let action): 24 | // return .chat(action) 25 | // case .contactsAuthorizationStatus(let status): 26 | // return .contactsAuthorizationStatus(status) 27 | // case .contactsResponse(let response): 28 | // return .contactsResponse(response) 29 | // case let .chatRoom(index: index, action: action): 30 | // return .chatRoom(index: index, action: action) 31 | // case let .chatWith(name: name, phoneNumber: phoneNumber): 32 | // return .chatWith(name: name, phoneNumber: phoneNumber) 33 | // } 34 | // } 35 | // } 36 | // 37 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactsEnvironment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsEnvironment.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 13.05.2021. 6 | // 7 | 8 | import Combine 9 | import ComposableArchitecture 10 | import CoreDataClient 11 | import CoreDataStore 12 | import Foundation 13 | 14 | 15 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ContactsView/ContactsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 12.05.2021. 6 | // 7 | 8 | import ChatView 9 | import ComposableArchitecture 10 | import Contacts 11 | 12 | import AddaSharedModels 13 | import BSON 14 | 15 | // extension ContactsState { 16 | // var view: ContactsView.ViewState { 17 | // ContactsView.ViewState( 18 | // alert: self.alert, 19 | // contacts: self.contacts, 20 | // invalidPermission: self.invalidPermission, 21 | // isLoading: self.isLoading 22 | // ) 23 | // } 24 | // } 25 | 26 | // swiftlint:disable line_length superfluous_disable_command 27 | extension ContactsReducer.State { 28 | public static let contactsPlaceholder = Self(isAuthorizedContacts: true) 29 | } 30 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ConversationClient/ConversationClient.swift: -------------------------------------------------------------------------------- 1 | // import Combine 2 | // import Foundation 3 | // import AddaSharedModels 4 | // import SwiftUI 5 | // import InfoPlist 6 | // import URLRouting 7 | // 8 | // public struct ConversationClient { 9 | // 10 | // public static let apiClient: URLRoutingClient = .live( 11 | // router: siteRouter.baseRequestData( 12 | // .init( 13 | // scheme: EnvironmentKeys.rootURL.scheme, 14 | // host: EnvironmentKeys.rootURL.host, 15 | // port: EnvironmentKeys.setPort(), 16 | // headers: [ 17 | // "Authorization": ["Bearer"] 18 | // ] 19 | // ) 20 | // ) 21 | // ) 22 | // 23 | // public typealias ConversationCreateHandler = @Sendable (ConversationCreate) async throws -> ConversationOutPut 24 | // public typealias AddUserToConversationHandler = @Sendable (AddUser) async throws -> AddUser 25 | // public typealias ConversationListHandler = @Sendable (QueryItem) async throws -> ConversationsResponse 26 | // public typealias ConversationFindHandler = @Sendable (String) async throws -> ConversationOutPut 27 | // 28 | // public let create: ConversationCreateHandler 29 | // public let addUserToConversation: AddUserToConversationHandler 30 | // public let list: ConversationListHandler 31 | // public let find: ConversationFindHandler 32 | // 33 | // public init( 34 | // create: @escaping ConversationCreateHandler, 35 | // addUserToConversation: @escaping AddUserToConversationHandler, 36 | // list: @escaping ConversationListHandler, 37 | // find: @escaping ConversationFindHandler 38 | // ) { 39 | // self.create = create 40 | // self.addUserToConversation = addUserToConversation 41 | // self.list = list 42 | // self.find = find 43 | // } 44 | // } 45 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ConversationClient/Mocks.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// Mocks.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 22.02.2021. 6 | //// 7 | // 8 | // import Combine 9 | // import Foundation 10 | // import FoundationExtension 11 | // 12 | // import AddaSharedModels 13 | // 14 | //// swiftlint:disable all 15 | // extension ConversationClient { 16 | // public static let happyPath = Self( 17 | // create: { _ in ConversationOutPut.runningDraff }, 18 | // addUserToConversation: { _ in AddUser.draff }, 19 | // list: { _ in ConversationsResponse.conversationResponseMock }, 20 | // find: { _ in ConversationOutPut.exploreAreaDraff } 21 | // ) 22 | // } 23 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ConversationClientLive/Live.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// ConversationAPI.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 22.02.2021. 6 | //// 7 | // 8 | // import Combine 9 | // 10 | // import Foundation 11 | // import FoundationExtension 12 | // import InfoPlist 13 | // import AddaSharedModels 14 | // import URLRouting 15 | // 16 | // extension ConversationClient { 17 | // public static var live: ConversationClient = 18 | // .init( 19 | // create: { input in 20 | // return try await ConversationClient.apiClient.decodedResponse( 21 | // for: .chatEngine(.conversations(.create(input: input))), 22 | // as: ConversationOutPut.self, 23 | // decoder: .iso8601 24 | // ).value 25 | // }, 26 | // addUserToConversation: { addUserIDs in 27 | // return try await ConversationClient.apiClient.decodedResponse( 28 | // for: .authEngine( 29 | // .users( 30 | // .user( 31 | // id: addUserIDs.usersId.hexString, 32 | // route: .conversations( 33 | // .conversation( 34 | // id: addUserIDs.conversationsId.hexString, 35 | // route: .joinuser 36 | // ) 37 | // ) 38 | // ) 39 | // ) 40 | // ) 41 | // , 42 | // as: AddUser.self, 43 | // decoder: .iso8601 44 | // ).value 45 | // }, 46 | // list: { query in 47 | // return try await ConversationClient.apiClient.decodedResponse( 48 | // for: .chatEngine(.conversations(.list(query: query))), 49 | // as: ConversationsResponse.self, 50 | // decoder: .iso8601 51 | // ).value 52 | // }, 53 | // find: { id in 54 | // return try await ConversationClient.apiClient.decodedResponse( 55 | // for: .chatEngine(.conversations(.conversation(id: id, route: .find))), 56 | // as: ConversationOutPut.self, 57 | // decoder: .iso8601 58 | // ).value 59 | // } 60 | // ) 61 | // 62 | // } 63 | // 64 | // extension Never: Encodable { 65 | // public func encode(to _: Encoder) throws { 66 | // fatalError("Never error called") 67 | // } 68 | // } 69 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ConversationsView/ConversationEnvironment.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// ConversationEnvironment.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 19.04.2021. 6 | //// 7 | 8 | // import Combine 9 | // import ComposableArchitecture 10 | // 11 | // Live 12 | // import AddaSharedModels 13 | // 14 | // import Foundation 15 | // 16 | // public struct ConversationEnvironment { 17 | // public let conversationClient: ConversationClient 18 | // public let websocketClient: WebSocketClient 19 | // public var backgroundQueue: AnySchedulerOf 20 | // public var mainQueue: AnySchedulerOf 21 | // 22 | // public init( 23 | // conversationClient: ConversationClient, 24 | // websocketClient: WebSocketClient, 25 | // backgroundQueue: AnySchedulerOf, 26 | // mainQueue: AnySchedulerOf 27 | // ) { 28 | // self.backgroundQueue = backgroundQueue 29 | // self.conversationClient = conversationClient 30 | // self.websocketClient = websocketClient 31 | // self.mainQueue = mainQueue 32 | // } 33 | // } 34 | // 35 | // extension ConversationEnvironment { 36 | // public static let live: ConversationEnvironment = .init( 37 | // conversationClient: .live, 38 | // websocketClient: .live, 39 | // backgroundQueue: .main, 40 | // mainQueue: .main 41 | // ) 42 | // } 43 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ConversationsView/ConversationsAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConversationAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 19.04.2021. 6 | // 7 | 8 | import ChatView 9 | import ComposableArchitecture 10 | import ContactsView 11 | import Foundation 12 | 13 | import AddaSharedModels 14 | import BSON 15 | 16 | extension Conversations.Action { 17 | // swiftlint:disable cyclomatic_complexity 18 | init(_ action: ConversationsView.ViewAction) { 19 | switch action { 20 | case .onAppear: 21 | self = .onAppear 22 | case .onDisAppear: 23 | self = .onDisAppear 24 | case let .conversationsResponse(res): 25 | self = .conversationsResponse(res) 26 | case let .fetchMoreConversationIfNeeded(currentItem): 27 | self = .fetchMoreConversationIfNeeded(currentItem: currentItem) 28 | case .alertDismissed: 29 | self = .alertDismissed 30 | case let .conversationTapped(conversationItem): 31 | self = .conversationTapped(conversationItem) 32 | case let .chat(action): 33 | self = .chat(action) 34 | case let .contacts(action): 35 | self = .contacts(action) 36 | case let .chatView(isPresented: isPresented): 37 | self = .chatView(isPresented: isPresented) 38 | case let .contactsView(isPresented: isPresented): 39 | self = .contactsView(isPresented: isPresented) 40 | case let .updateLastConversation(messageItem): 41 | self = .updateLastConversation(messageItem) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ConversationsView/ConversationsState.swift: -------------------------------------------------------------------------------- 1 | import ChatView 2 | import ComposableArchitecture 3 | import ContactsView 4 | import Foundation 5 | import AddaSharedModels 6 | 7 | extension Conversations.State { 8 | public static let placholderConversations = Self( 9 | isLoadingPage: true, 10 | conversations: .init(uniqueElements: ConversationOutPut.conversationsMock), 11 | chatState: .init( 12 | conversation: .exploreAreaDraff, currentUser: .withFirstName, 13 | websocketState: .init(user: .withFirstName) 14 | ), 15 | websocketState: .init(user: .withFirstName) 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /AddameSPM/Sources/CoreDataClient/CoreDataClient.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import CoreData 3 | import CoreDataStore 4 | import Foundation 5 | import AddaSharedModels 6 | import BSON 7 | 8 | //public final class CoreDataClient { 9 | // public var contactClient: ContactClient 10 | // public var contacts = [ContactOutPut]() 11 | // 12 | // public init(contactClient: ContactClient) { 13 | // self.contactClient = contactClient 14 | // } 15 | // 16 | // public func getContacts(contacts: MobileNumbersInput) async throws -> [ContactOutPut] { 17 | // let defaultContacts = Set(contacts.mobileNumber).sorted() 18 | // let afterRemoveDuplicationContacts = MobileNumbersInput(mobileNumber: defaultContacts) 19 | // return try await contactClient.getRegisterUsersFromServer(afterRemoveDuplicationContacts) 20 | // .map { user in 21 | // return ContactOutPut( 22 | // id: ObjectId(), 23 | // userId: user.id, 24 | // identifier: user.id.hexString, 25 | // phoneNumber: user.phoneNumber ?? "", 26 | // fullName: user.fullName, 27 | // avatar: user.lastAvatarURLString, 28 | // isRegister: true 29 | // ) 30 | // } 31 | // } 32 | //} 33 | -------------------------------------------------------------------------------- /AddameSPM/Sources/CoreDataStore/CoreDataStore.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | public struct CoreDataStore { 4 | public static let shared = CoreDataStore() 5 | 6 | public var moc: NSManagedObjectContext { 7 | return container.viewContext 8 | } 9 | 10 | public let container: NSPersistentContainer 11 | 12 | @discardableResult 13 | public init?(inMemory: Bool = false) { 14 | guard let modelURL = Bundle.module.url(forResource: "AddaModel", withExtension: ".momd") else { 15 | return nil 16 | } 17 | guard let model = NSManagedObjectModel(contentsOf: modelURL) else { return nil } 18 | 19 | container = NSPersistentContainer(name: "AddaModel", managedObjectModel: model) 20 | 21 | if inMemory, let container = container.persistentStoreDescriptions.first { 22 | container.url = URL(fileURLWithPath: "/dev/null") 23 | } 24 | 25 | container.loadPersistentStores(completionHandler: { _, error in 26 | if let error = error as NSError? { 27 | fatalError("Unresolved error \(error), \(error.userInfo)") 28 | } 29 | }) 30 | 31 | container.viewContext.automaticallyMergesChangesFromParent = true 32 | container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 33 | } 34 | 35 | // MARK: - Core Data Saving support 36 | 37 | public func saveContext() { 38 | let context = container.viewContext 39 | if context.hasChanges { 40 | do { 41 | try context.save() 42 | } catch { 43 | let nserror = error as NSError 44 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /AddameSPM/Sources/CoreDataStore/Entities/Contacts/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 CoreData 10 | import Foundation 11 | 12 | @objc(ContactEntity) 13 | public class ContactEntity: NSManagedObject {} 14 | -------------------------------------------------------------------------------- /AddameSPM/Sources/CoreDataStore/Entities/Contacts/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 CoreData 10 | import Foundation 11 | 12 | extension ContactEntity { 13 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 14 | return NSFetchRequest(entityName: "ContactEntity") 15 | } 16 | 17 | @NSManaged public var avatar: String? 18 | @NSManaged public var fullName: String 19 | @NSManaged public var id: String? 20 | @NSManaged public var identifier: String 21 | @NSManaged public var isRegister: Bool 22 | @NSManaged public var phoneNumber: String 23 | @NSManaged public var userId: String 24 | } 25 | 26 | extension ContactEntity: Identifiable {} 27 | -------------------------------------------------------------------------------- /AddameSPM/Sources/CoreDataStore/Entities/Contacts/ContactEntityExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactEntityExtension.swift 3 | // AddaMeIOS 4 | // 5 | // Created by Saroar Khandoker on 01.12.2020. 6 | // 7 | 8 | import CoreData 9 | import Foundation 10 | import FoundationExtension 11 | import AddaSharedModels 12 | 13 | extension ContactEntity { 14 | static func allContactsFetchRequest() -> NSFetchRequest { 15 | let request: NSFetchRequest = ContactEntity.fetchRequest() 16 | request.sortDescriptors = [NSSortDescriptor(key: "phoneNumber", ascending: true)] 17 | print(#line, request) 18 | return request 19 | } 20 | } 21 | 22 | extension ContactEntity { 23 | static var registerContactsFetchRequest: NSFetchRequest { 24 | let request: NSFetchRequest = ContactEntity.fetchRequest() 25 | request.predicate = NSPredicate(format: "isRegister == true") 26 | request.sortDescriptors = [NSSortDescriptor(key: "fullName", ascending: true)] 27 | 28 | return request 29 | } 30 | } 31 | 32 | extension ContactEntity: ManagedModel { 33 | public static var defaultPredicate: NSPredicate { return NSPredicate(value: true) } 34 | 35 | public static func findOrCreate(withData data: APIData, in context: NSManagedObjectContext) 36 | -> ContactEntity { 37 | guard let content = data as? ContactOutPut else { 38 | fatalError("Incorrent API response") 39 | } 40 | 41 | let predicate = NSPredicate(format: "%K == %@", #keyPath(phoneNumber), content.phoneNumber) 42 | let contact = ContactEntity.findOrCreate(in: context, matching: predicate) { contact in 43 | contact.id = content.id.hexString 44 | contact.fullName = content.fullName ?? String.empty 45 | contact.avatar = content.avatar 46 | contact.identifier = content.identifier ?? "" 47 | contact.isRegister = content.isRegister ?? false 48 | contact.userId = content.userId.hexString 49 | contact.phoneNumber = content.phoneNumber 50 | } 51 | 52 | contact.id = content.id.hexString 53 | contact.fullName = content.fullName ?? String.empty 54 | contact.avatar = content.avatar 55 | contact.identifier = content.identifier ?? "" 56 | contact.isRegister = content.isRegister ?? false 57 | contact.userId = content.userId.hexString 58 | contact.phoneNumber = content.phoneNumber 59 | 60 | return contact 61 | } 62 | 63 | public static var defaultSortDescriptors: [NSSortDescriptor] { 64 | return [NSSortDescriptor(key: #keyPath(isRegister), ascending: true)] 65 | } 66 | } 67 | 68 | // extension ContactEntity { 69 | // public func contact() -> ContactInOutPut { 70 | // return ContactInOutPut( 71 | // id: id, 72 | // identifier: identifier, 73 | // userId: userId, 74 | // phoneNumber: phoneNumber, 75 | // fullName: fullName, avatar: avatar, 76 | // isRegister: isRegister 77 | // ) 78 | // } 79 | // } 80 | -------------------------------------------------------------------------------- /AddameSPM/Sources/CoreDataStore/Models/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 | -------------------------------------------------------------------------------- /AddameSPM/Sources/DeviceClient/DeviceClicnt.swift: -------------------------------------------------------------------------------- 1 | // import Foundation 2 | // import FoundationExtension 3 | // import AddaSharedModels 4 | // import InfoPlist 5 | // import URLRouting 6 | // 7 | // public struct DeviceClient { 8 | // 9 | // public static let apiClient: URLRoutingClient = .live( 10 | // router: siteRouter.baseRequestData( 11 | // .init( 12 | // scheme: EnvironmentKeys.rootURL.scheme, 13 | // host: EnvironmentKeys.rootURL.host, 14 | // port: EnvironmentKeys.setPort(), 15 | // headers: ["Authorization": ["Bearer "]] 16 | // ) 17 | // ) 18 | // ) 19 | // 20 | // public typealias DeviceCUHandler = @Sendable (DeviceInOutPut) async throws -> DeviceInOutPut 21 | // 22 | // public let dcu: DeviceCUHandler 23 | // 24 | // public init(dcu: @escaping DeviceCUHandler) { 25 | // self.dcu = dcu 26 | // } 27 | // 28 | // } 29 | // 30 | //// Mock 31 | // extension DeviceClient { 32 | // public static let empty = Self( 33 | // dcu: { _ in .empty } 34 | // ) 35 | // 36 | // public static let happyPath = Self( 37 | // dcu: { _ in .draff } 38 | // ) 39 | // } 40 | // 41 | //// Live 42 | // extension DeviceClient { 43 | // public static var live: DeviceClient = .init( 44 | // dcu: { input in 45 | // return try await DeviceClient.apiClient.decodedResponse( 46 | // for: .authEngine(.devices(.createOrUpdate(input: input))), 47 | // as: DeviceInOutPut.self, 48 | // decoder: .iso8601 49 | // ).value 50 | // } 51 | // ) 52 | // 53 | // } 54 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventClient/EventClient.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import Foundation 3 | import AddaSharedModels 4 | import SwiftUI 5 | import URLRouting 6 | import InfoPlist 7 | 8 | public struct EventClient { 9 | 10 | public static let apiClient: URLRoutingClient = .live( 11 | router: siteRouter.baseRequestData( 12 | .init( 13 | scheme: EnvironmentKeys.rootURL.scheme, 14 | host: EnvironmentKeys.rootURL.host, 15 | port: EnvironmentKeys.setPort(), 16 | headers: [ 17 | "Authorization": ["Bearer "] 18 | ] 19 | ) 20 | ) 21 | ) 22 | 23 | public typealias EventFetchHandler = @Sendable (EventPageRequest) async throws -> EventsResponse 24 | public typealias EventCreateHandler = @Sendable (EventInput) async throws -> EventResponse 25 | public typealias CatrgoriesFetchHandler = @Sendable () async throws -> CategoriesResponse 26 | 27 | public let create: EventCreateHandler 28 | public let events: EventFetchHandler 29 | public let categoriesFetch: CatrgoriesFetchHandler 30 | 31 | public init( 32 | events: @escaping EventFetchHandler, 33 | create: @escaping EventCreateHandler, 34 | categoriesFetch: @escaping CatrgoriesFetchHandler 35 | ) { 36 | self.events = events 37 | self.create = create 38 | self.categoriesFetch = categoriesFetch 39 | } 40 | } 41 | 42 | extension URL { 43 | public static var empty = Self(string: "")! 44 | } 45 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventClient/Mocks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventClient.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 25.01.2021. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | 11 | import AddaSharedModels 12 | 13 | // swiftlint:disable all 14 | extension EventClient { 15 | private static let data = Date(timeIntervalSince1970: 0) 16 | public static let empty = Self( 17 | events: { _ in EventsResponse.draffEmpty }, 18 | create: { _ in EventResponse.emptyDraff }, 19 | categoriesFetch: { CategoriesResponse.draff } 20 | ) 21 | 22 | public static let happyPath = Self( 23 | events: { _ in EventsResponse.draff }, 24 | create: { _ in EventResponse.runningDraff }, 25 | categoriesFetch: { CategoriesResponse.draff } 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventClientLive/Live.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventClientLive.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 25.01.2021. 6 | // 7 | 8 | import Combine 9 | 10 | import Foundation 11 | import InfoPlist 12 | import AddaSharedModels 13 | import URLRouting 14 | 15 | extension EventClient { 16 | 17 | public static var live: EventClient = .init( 18 | events: { query in 19 | return try await EventClient.apiClient.decodedResponse( 20 | for: .eventEngine(.events(.list(query: query))), 21 | as: EventsResponse.self, 22 | decoder: .ISO8601JSONDecoder 23 | ).value 24 | }, 25 | create: { input in 26 | return try await EventClient.apiClient.decodedResponse( 27 | for: .eventEngine(.events(.create(eventInput: input))), 28 | as: EventResponse.self, 29 | decoder: .ISO8601JSONDecoder 30 | ).value 31 | }, 32 | categoriesFetch: { 33 | return try await EventClient.apiClient.decodedResponse( 34 | for: .eventEngine(.categories(.list)), 35 | as: CategoriesResponse.self, 36 | decoder: .ISO8601JSONDecoder 37 | ).value 38 | } 39 | ) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventFormView/EventFormAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventFormAction.swift 3 | // EventFormAction 4 | // 5 | // Created by Saroar Khandoker on 06.08.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import Foundation 10 | import MapKit 11 | import MapView 12 | import AddaSharedModels 13 | import SwiftUI 14 | import BSON 15 | 16 | extension HangoutForm.Action { 17 | // swiftlint:disable cyclomatic_complexity 18 | init(action: EventFormView.ViewAction) { 19 | switch action { 20 | case .onAppear: 21 | self = .onAppear 22 | case .onDisappear: 23 | self = .onDisappear 24 | case let .titleChanged(string): 25 | self = .titleChanged(string) 26 | case let .textFieldHeightChanged(value): 27 | self = .textFieldHeightChanged(value) 28 | case let .selectedDurations(duration): 29 | self = .selectedDurations(duration) 30 | case let .selectedDurationIndex(int): 31 | self = .selectedDurationIndex(int) 32 | case let .selectedCategory(category): 33 | self = .selectedCategory(category) 34 | case let .showCategorySheet(isPresent): 35 | self = .showCategorySheet(isPresent) 36 | case let .liveLocationToggleChanged(liveLocationEnabled): 37 | self = .liveLocationToggleChanged(liveLocationEnabled) 38 | case let .isLocationSearch(navigate: active): 39 | self = .isLocationSearch(navigate: active) 40 | case let .locationSearch(action): 41 | self = .locationSearch(action) 42 | case .submitButtonTapped: 43 | self = .submitButtonTapped 44 | case .confirmationCategoriesDialogButtonTapped: 45 | self = .confirmationCategoriesDialogButtonTapped 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventFormView/EventFormEnvironment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventFormEnvironment.swift 3 | // EventFormEnvironment 4 | // 5 | // Created by Saroar Khandoker on 06.08.2021. 6 | // 7 | 8 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventFormView/EventFormState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventFormState.swift 3 | // EventFormState 4 | // 5 | // Created by Saroar Khandoker on 06.08.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import ComposableArchitectureHelpers 10 | import Foundation 11 | import MapKit 12 | import MapView 13 | import AddaSharedModels 14 | import BSON 15 | 16 | extension HangoutForm.State { 17 | 18 | private static var placeMarkDemo: Placemark { 19 | let location = CLLocationCoordinate2D(latitude: 60.02071653, longitude: 30.38745188) 20 | let mkPlacemark = MKPlacemark(coordinate: location) 21 | let mk = MKMapItem(placemark: mkPlacemark) 22 | let placemark = Placemark(rawValue: mk.placemark) 23 | return placemark 24 | } 25 | 26 | public static let validEventForm = Self( 27 | title: "", 28 | durationRawValue: "7200", 29 | placeMark: placeMarkDemo, 30 | eventAddress: "188839, Первомайское, СНТ Славино-2 Поселок, 31 Первомайское Россия", 31 | categories: .init(uniqueElements: [CategoryResponse.exploreAreaDraff]), 32 | isPostRequestOnFly: false, 33 | isEventCreatedSuccessfully: true, 34 | currentUser: .withFirstName 35 | ) 36 | 37 | public static let invalidEventForm = Self( 38 | title: "Walk around Walk around Walk around Walk around Walk around", 39 | durationRawValue: "4hr", 40 | placeMark: placeMarkDemo, 41 | eventAddress: "", 42 | isPostRequestOnFly: false, 43 | isEventCreatedSuccessfully: false, 44 | currentUser: .withNumber 45 | ) 46 | } 47 | 48 | extension CategoryResponse: Identifiable {} 49 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventView/EventsAction.swift: -------------------------------------------------------------------------------- 1 | import ChatView 2 | import ComposableArchitecture 3 | import ComposableCoreLocation 4 | import HangoutDetailsFeature 5 | import EventFormView 6 | import Foundation 7 | 8 | import MapKit 9 | import AddaSharedModels 10 | import AdSupport 11 | import AppTrackingTransparency 12 | 13 | // swiftlint:disable:next superfluous_disable_command 14 | extension Hangouts.Action { 15 | // init(action: NewGameView.ViewAction) { 16 | 17 | // swiftlint:disable:next cyclomatic_complexity function_body_length superfluous_disable_command 18 | init(action: HangoutsView.ViewAction) { 19 | switch action { 20 | case .alertDismissed: 21 | self = .alertDismissed 22 | case .dismissHangoutDetails: 23 | self = .dismissHangoutDetails 24 | case let .hangoutFormView(isNavigate): 25 | self = .hangoutFormView(isNavigate: isNavigate) 26 | case let .hangoutForm(hFormAction): 27 | self = .hangoutForm(hFormAction) 28 | case let .chatView(isNavigate: bool): 29 | self = .chatView(isNavigate: bool) 30 | case let .chat(action): 31 | self = .chat(action) 32 | case let .event(index: index, action: action): 33 | self = .event(index: index, action: action) 34 | case .currentLocationButtonTapped: 35 | self = .currentLocationButtonTapped 36 | case let .eventTapped(event): 37 | self = .eventTapped(event) 38 | case .popupSettings: 39 | self = .popupSettings 40 | case .dismissEvent: 41 | self = .dismissEvent 42 | case .fetchEventOnAppear: 43 | self = .fetchEventOnAppear 44 | case .onAppear: 45 | self = .onAppear 46 | case .onDisAppear: 47 | self = .onDisAppear 48 | case let .hangoutDetailsSheet(isPresented: isPresented): 49 | self = .hangoutDetailsSheet(isPresented: isPresented) 50 | case let .hangoutDetails(action): 51 | self = .hangoutDetails(action) 52 | case let .fetchMoreEventsIfNeeded(item: item): 53 | self = .fetchMoreEventsIfNeeded(item: item) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventView/EventsState.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AddameSPM/Sources/EventView/EventsStateMocks.swift: -------------------------------------------------------------------------------- 1 | import MapKit 2 | import ComposableCoreLocation 3 | import AddaSharedModels 4 | 5 | // swiftlint:disable all 6 | extension Hangouts.State { 7 | 8 | public static let placeholderEvents = Self( 9 | isConnected: true, 10 | isLoadingPage: true, 11 | location: Location( 12 | altitude: 0, 13 | coordinate: CLLocationCoordinate2D(latitude: 60.020532228306031, longitude: 30.388014239849944), 14 | course: 0, 15 | horizontalAccuracy: 0, 16 | speed: 0, 17 | timestamp: Date(timeIntervalSince1970: 0), 18 | verticalAccuracy: 0 19 | ), 20 | events: .init(uniqueElements: EventsResponse.draff.items), 21 | websocketState: .init(user: .withFirstName) 22 | ) 23 | 24 | public static let fetchEvents = Self( 25 | isConnected: true, 26 | isLoadingPage: false, 27 | location: Location( 28 | altitude: 0, 29 | coordinate: CLLocationCoordinate2D(latitude: 60.020532228306031, longitude: 30.388014239849944), 30 | course: 0, 31 | horizontalAccuracy: 0, 32 | speed: 0, 33 | timestamp: Date(timeIntervalSince1970: 0), 34 | verticalAccuracy: 0 35 | ), 36 | events: .init(uniqueElements: EventsResponse.draff.items), 37 | websocketState: .init(user: .withFirstName) 38 | ) 39 | 40 | 41 | public static let eventForRow = Self( 42 | location: Location( 43 | altitude: 0, 44 | coordinate: CLLocationCoordinate2D(latitude: 60.020532228306031, longitude: 30.388014239849944), 45 | course: 0, 46 | horizontalAccuracy: 0, 47 | speed: 0, 48 | timestamp: Date(timeIntervalSince1970: 0), 49 | verticalAccuracy: 0 50 | ), 51 | event: EventResponse.exploreAreaDraff, 52 | websocketState: .init(user: .withFirstName) 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/EventDetailsAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HangoutDetailsAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 12.07.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import KeychainClient 10 | import MapView 11 | import AddaSharedModels 12 | 13 | extension HangoutDetails.Action { 14 | // extension HangoutForm.Action { 15 | // // swiftlint:disable cyclomatic_complexity 16 | // init(action: EventFormView.ViewAction) { 17 | // switch action { 18 | // } 19 | // } 20 | // } 21 | static func view(_ localAction: HangoutDetailsView.ViewAction) -> Self { 22 | switch localAction { 23 | case .onAppear: 24 | return .onAppear 25 | case .alertDismissed: 26 | return .alertDismissed 27 | case let .moveToChatRoom(bool): 28 | return .moveToChatRoom(bool) 29 | case let .updateRegion(coordinateRegion): 30 | return .updateRegion(coordinateRegion) 31 | case .startChat(let bool): 32 | return .startChat(bool) 33 | case .askJoinRequest(let boolean): 34 | return .askJoinRequest(boolean) 35 | case .joinToEvent(let taskResult): 36 | return .joinToEvent(taskResult) 37 | case .conversationResponse(let taskResult): 38 | return .conversationResponse(taskResult) 39 | case .userResponse(let userOutput): 40 | return .userResponse(userOutput) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/EventDetailsOverlayReducer.swift: -------------------------------------------------------------------------------- 1 | // public let eventDetailsOverlayReducer = Reducer< 2 | // HangoutDetailsOverlayState, HangoutDetailsOverlayAction, HangoutDetailsEnvironment 3 | // > { state, action, environment in 4 | // switch action { 5 | // case .onAppear: 6 | // 7 | //// return environment.conversationClient.find("", state.event.conversationsId.hexString) 8 | //// .receive(on: environment.mainQueue) 9 | //// .catchToEffect() 10 | //// .map(HangoutDetailsOverlayAction.conversationResponse) 11 | // 12 | // return .none 13 | //// .task { [state] in 14 | //// do { 15 | //// let conversationOutput = try await environment.conversationClient 16 | //// .find(state.event.conversationsId.hexString) 17 | //// 18 | //// return HangoutDetailsOverlayAction.conversationResponse(.success(conversationOutput)) 19 | //// } catch { 20 | //// return HangoutDetailsOverlayAction.conversationResponse(.failure(.custom("", error))) 21 | //// } 22 | //// } 23 | // 24 | // case .alertDismissed: 25 | // state.alert = nil 26 | // return .none 27 | // 28 | // case let .startChat(bool): 29 | // return .none 30 | // 31 | // case let .askJoinRequest(bool): 32 | // guard let currentUSER: UserOutput = KeychainClient.readCodable(.user), //.loadCodable(for: .), 33 | // let conversation = state.conversation, 34 | // let usersId = currentUSER.id 35 | // else { 36 | // // send logs 37 | // return .none 38 | // } 39 | // 40 | // let conversationId = conversation.id 41 | // state.isMovingChatRoom = bool 42 | // let adduser = AddUser( 43 | // conversationsId: conversationId, 44 | // usersId: usersId 45 | // ) 46 | // 47 | //// return environment.conversationClient 48 | //// .addUserToConversation(adduser, "\(conversationId)/users/\(usersId)") 49 | //// .receive(on: environment.mainQueue) 50 | //// .catchToEffect() 51 | //// .map(HangoutDetailsOverlayAction.joinToEvent) 52 | // 53 | // return .task { 54 | // do { 55 | // let addMe = try await environment.conversationClient.addUserToConversation(adduser) 56 | // return HangoutDetailsOverlayAction.joinToEvent(.success(addMe)) 57 | // } catch { 58 | // return HangoutDetailsOverlayAction.joinToEvent(.failure(.custom("", error))) 59 | // } 60 | // } 61 | // 62 | // case let .joinToEvent(.success(response)): 63 | // print(#line, response) 64 | // return .none 65 | // case let .joinToEvent(.failure(error)): 66 | // 67 | // return .none 68 | // 69 | // case let .conversationResponse(.success(conversationItem)): 70 | // state.conversation = conversationItem 71 | // return .none 72 | // case let .conversationResponse(.failure(error)): 73 | // state.alert = .init(title: TextState("Something went wrong please try again later")) 74 | // return .none 75 | // } 76 | // } 77 | -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/EventDetailsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HangoutDetailsState.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 12.07.2021. 6 | // 7 | 8 | import ChatView 9 | import ComposableArchitecture 10 | import MapKit 11 | import MapView 12 | import AddaSharedModels 13 | 14 | 15 | extension HangoutDetails.State { 16 | public static let coordinate = CLLocationCoordinate2D( 17 | latitude: 60.00380571585201, longitude: 30.399472870547118 18 | ) 19 | 20 | public static let event = EventResponse.bicyclingDraff 21 | 22 | public static let region = CoordinateRegion( 23 | center: coordinate, 24 | span: MKCoordinateSpan(latitudeDelta: 0.03, longitudeDelta: 0.03) 25 | ) 26 | 27 | public static let placeHolderEvent = Self( 28 | 29 | event: event, 30 | pointsOfInterest: [.init(coordinate: coordinate, subtitle: "Awesome", title: "Cool")], 31 | region: region, 32 | chatMembers: 3 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/Resources/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/Resources/Media.xcassets/hangout_dt.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "hangout_dt.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/Resources/Media.xcassets/hangout_dt.imageset/hangout_dt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/AddameSPM/Sources/HangoutDetailsFeature/Resources/Media.xcassets/hangout_dt.imageset/hangout_dt.jpg -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/Resources/hangout_bc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/AddameSPM/Sources/HangoutDetailsFeature/Resources/hangout_bc.png -------------------------------------------------------------------------------- /AddameSPM/Sources/HangoutDetailsFeature/Resources/hangout_dt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/AddameSPM/Sources/HangoutDetailsFeature/Resources/hangout_dt.jpg -------------------------------------------------------------------------------- /AddameSPM/Sources/LocationSearchClient/Client.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalSearchClient.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.07.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import MapKit 10 | 11 | public struct LocalSearchClient { 12 | 13 | public var search: @Sendable (MKLocalSearch.Request) -> AsyncStream 14 | 15 | public init(search: @escaping @Sendable (MKLocalSearch.Request) -> AsyncStream) { 16 | self.search = search 17 | } 18 | 19 | public struct Error: Swift.Error, Equatable { 20 | public init() {} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AddameSPM/Sources/LocationSearchClient/Live.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Live.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.07.2021. 6 | // 7 | 8 | import Combine 9 | import ComposableArchitecture 10 | import MapKit 11 | 12 | extension LocalSearchClient { 13 | public static let live = LocalSearchClient(search: { request in 14 | AsyncStream { continuation in 15 | let search = MKLocalSearch(request: request) 16 | search.start { response, error in 17 | if let response = response { 18 | // Yield a success value to the stream 19 | continuation.yield(with: .success(LocalSearchResponse(response: response))) 20 | continuation.finish() 21 | } else if let error = error { 22 | // Yield an error to the stream 23 | // continuation.yield(with: .failure(Never)) 24 | print("Unexpected state: response and error \(error)") 25 | } else { 26 | // Handle unexpected state 27 | fatalError("Unexpected state: response and error are both nil.") 28 | } 29 | 30 | continuation.finish() 31 | } 32 | } 33 | }) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /AddameSPM/Sources/LocationSearchClient/Mock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocks.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.07.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import MapKit 10 | 11 | extension LocalSearchClient { 12 | public static func unimplemented( 13 | search: @Sendable @escaping (MKLocalSearch.Request) -> AsyncStream = { _ in 14 | AsyncStream { continuation in 15 | // Here, you can send a mock response or end the stream immediately 16 | // For example, ending the stream immediately: 17 | continuation.finish() 18 | 19 | // Alternatively, you can yield a mock LocalSearchResponse if needed 20 | // continuation.yield(LocalSearchResponse(/* Mock data */)) 21 | } 22 | } 23 | ) -> Self { 24 | Self(search: search) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AddameSPM/Sources/Logger/Extension+Logger.swift: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | extension Logger { 4 | private static var subsystem = Bundle.main.bundleIdentifier! 5 | 6 | /// Logs the view cycles like viewDidLoad. 7 | static let viewCycle = Logger(subsystem: subsystem, category: "viewcycle") 8 | } 9 | -------------------------------------------------------------------------------- /AddameSPM/Sources/MapView/LocationEnvironment.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AddameSPM/Sources/MapView/LocationSearchAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationSearchAction.swift 3 | // LocationSearchAction 4 | // 5 | // Created by Saroar Khandoker on 09.08.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import MapKit 10 | import SwiftUI 11 | 12 | extension LocationSearch.Action { 13 | // swiftlint:disable cyclomatic_complexity superfluous_disable_command 14 | static func view(_ localAction: LocationSearchView.ViewAction) -> Self { 15 | switch localAction { 16 | case .onAppear: 17 | return onAppear 18 | case .onDisappear: 19 | return onDisappear 20 | case .alertDismissed: 21 | return alertDismissed 22 | case let .searchTextInputChanged(inputText): 23 | return searchTextInputChanged(inputText) 24 | case let .textFieldHeightChanged(height): 25 | return textFieldHeightChanged(height) 26 | case let .isEditing(trigger): 27 | return isEditing(trigger) 28 | case let .locationSearchManager(action): 29 | return locationSearchManager(action) 30 | case let .cleanSearchText(isClean): 31 | return .cleanSearchText(isClean) 32 | case let .didSelect(address: results): 33 | return .didSelect(address: results) 34 | case let .pointOfInterest(index: index, address: address): 35 | return .pointOfInterest(index: index, address: address) 36 | case .region(let cr): 37 | return .region(cr) 38 | case .backToformView: 39 | return .backToformView 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AddameSPM/Sources/MapView/LocationSearchState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationSearchState.swift 3 | // LocationSearchState 4 | // 5 | // Created by Saroar Khandoker on 09.08.2021. 6 | // 7 | 8 | import Combine 9 | import ComposableArchitecture 10 | import Foundation 11 | import MapKit 12 | import Network 13 | import SwiftUI 14 | import SwiftUIExtension 15 | import AddaSharedModels 16 | 17 | extension LocationSearch.State { 18 | public static let locationSearchPlacholder = Self( 19 | searchTextInput: "", 20 | placeMark: Placemark(coordinate: CLLocationCoordinate2D(latitude: 60.006 , longitude: 30.38752), title: "улица Вавиловых, 8 к1, Saint Petersburg, Russia, 195257") 21 | ) 22 | // "Тихорецкий проспект, 3БЕ Санкт-Петербург, Россия, 194064" 23 | } 24 | 25 | extension MKLocalSearchCompletion: Identifiable {} 26 | -------------------------------------------------------------------------------- /AddameSPM/Sources/MyEventsView/EventRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 15.06.2022. 6 | // 7 | 8 | import SwiftUI 9 | import ComposableArchitecture 10 | import AddaSharedModels 11 | 12 | public struct MyEventRowView: View { 13 | @Environment(\.colorScheme) var colorScheme 14 | 15 | public init(store: Store) { 16 | self.store = store 17 | } 18 | 19 | public let store: Store 20 | 21 | public var body: some View { 22 | WithViewStore(self.store, observe: { $0 }) { viewStore in 23 | VStack(alignment: .leading) { 24 | Group { 25 | Text(viewStore.name) 26 | .font(.system(.title, design: .rounded)) 27 | .lineLimit(2) 28 | .padding(.top, 8) 29 | .padding(.bottom, 3) 30 | 31 | Text(viewStore.addressName) 32 | .font(.system(.body, design: .rounded)) 33 | .foregroundColor(.blue) 34 | .padding(.bottom, 16) 35 | } 36 | .frame(maxWidth: .infinity, alignment: .leading) 37 | .padding(.horizontal, 16) 38 | } 39 | .background( 40 | RoundedRectangle(cornerRadius: 10) 41 | .foregroundColor( 42 | colorScheme == .dark 43 | ? Color( 44 | #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260520637, alpha: 1)) 45 | : Color( 46 | #colorLiteral( 47 | red: 0.8374180198, green: 0.8374378085, blue: 0.8374271393, alpha: 0.5))) 48 | ) 49 | .padding([.leading, .trailing], 16) 50 | .padding([.top, .bottom], 5) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /AddameSPM/Sources/MyEventsView/MyEventsAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyEventsAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 15.06.2022. 6 | // 7 | 8 | import AddaSharedModels 9 | 10 | //public enum MyEventAction: Equatable {} 11 | // 12 | //public enum MyEventsAction: Equatable { 13 | // case onApper 14 | // case event(id: EventResponse.ID, action: MyEventAction) 15 | // case myEventsResponse(TaskResult) 16 | //} 17 | -------------------------------------------------------------------------------- /AddameSPM/Sources/MyEventsView/MyEventsListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 15.06.2022. 6 | // 7 | 8 | import SwiftUI 9 | import ComposableArchitecture 10 | import AddaSharedModels 11 | 12 | public struct MyEventsListView: View { 13 | let store: StoreOf 14 | 15 | public init(store: StoreOf) { 16 | self.store = store 17 | } 18 | 19 | public var body: some View { 20 | 21 | WithViewStore(self.store, observe: { $0 }) { viewstore in 22 | VStack { 23 | Text("My Events").font(.title) 24 | ForEachStore( 25 | self.store.scope(state: \.myEvents, action: MyEvents.Action.event) 26 | ) { eventStore in 27 | WithViewStore(eventStore, observe: { $0 }) { _ in 28 | // Button(action: { viewStore.send(.eventTapped(eventViewStore.state)) }) { 29 | MyEventRowView(store: eventStore) 30 | // } 31 | // .buttonStyle(PlainButtonStyle()) 32 | } 33 | // .padding([.leading, .trailing], 30) 34 | } 35 | } 36 | .onAppear { viewstore.send(.onApper) } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ProfileView/File.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// File.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 18.10.2021. 6 | //// 7 | // 8 | // import SwiftUI 9 | // import SwiftUIExtension 10 | // 11 | // struct ImageSlider: View { 12 | // 13 | // // 1 14 | // private let images = ["1", "2", "3", "4"] 15 | // 16 | // var body: some View { 17 | // // 2 18 | // TabView { 19 | // ForEach(images, id: \.self) { item in 20 | // // 3 21 | // Image(item) 22 | // .resizable() 23 | // .scaledToFill() 24 | // } 25 | // } 26 | // .tabViewStyle(PageTabViewStyle()) 27 | // } 28 | // } 29 | // 30 | //// struct ImageSlider_Previews: PreviewProvider { 31 | //// static var previews: some View { 32 | //// // 4 33 | //// ImageSlider() 34 | //// .previewLayout(.fixed(width: 400, height: 300)) 35 | //// } 36 | //// } 37 | // 38 | //// struct ContentView: View { 39 | //// var body: some View { 40 | //// 41 | //// // 1 42 | //// NavigationView { 43 | //// // 2 44 | //// List { 45 | //// ImageSlider() 46 | //// .frame(height: 300) 47 | //// .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) 48 | //// } //: List 49 | //// .navigationBarTitle("Image Slider", displayMode: .large) 50 | //// } //: Navigation View 51 | //// } 52 | //// 53 | //// } 54 | // 55 | // struct ContentView: View { 56 | // @State var index = 0 57 | // 58 | // var images = ["person", "ant", "ladybug", "leaf"] 59 | // 60 | // var body: some View { 61 | // VStack(spacing: 0) { 62 | // PagingView(index: $index.animation(), maxIndex: images.count - 1) { 63 | // ForEach(self.images, id: \.self) { imageName in 64 | // Image(systemName: imageName) 65 | // .resizable() 66 | // .scaledToFit() 67 | // .padding() 68 | // } 69 | // } 70 | // .aspectRatio(3 / 4, contentMode: .fit) 71 | // .background(Color.red) 72 | // Spacer() 73 | // 74 | //// PagingView(index: $index.animation(), maxIndex: images.count - 1) { 75 | //// ForEach(self.images, id: \.self) { imageName in 76 | //// Image(systemName: imageName) 77 | //// .resizable() 78 | //// .scaledToFill() 79 | //// } 80 | //// } 81 | //// .aspectRatio(3/4, contentMode: .fit) 82 | //// .clipShape(RoundedRectangle(cornerRadius: 15)) 83 | // 84 | //// Stepper("Index: \(index)", value: $index.animation(.easeInOut), in: 0...images.count-1) 85 | //// .font(Font.body.monospacedDigit()) 86 | // } 87 | // } 88 | // } 89 | //// 90 | //// struct ContentView_Previews: PreviewProvider { 91 | //// static var previews: some View { 92 | //// ContentView() 93 | //// } 94 | //// } 95 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ProfileView/ProfileAction.swift: -------------------------------------------------------------------------------- 1 | 2 | import AddaSharedModels 3 | import SwiftUI 4 | import ImagePicker 5 | import MyEventsView 6 | 7 | extension Profile.Action { 8 | // swiftlint:disable:next cyclomatic_complexity 9 | public static func view(_ localAction: ProfileView.ViewAction) -> Self { 10 | // swiftlint:disable:next superfluous_disable_command 11 | switch localAction { 12 | case .alertDismissed: 13 | return .alertDismissed 14 | case .isUploadingImage: 15 | return .isUploadingImage 16 | case let .isImagePicker(isPresented: presented): 17 | return .isImagePicker(isPresented: presented) 18 | case .moveToSettingsView: 19 | return .moveToAuthView 20 | case let .settingsView(isNavigate: present): 21 | return .settingsView(isNavigate: present) 22 | case .fetchMyData: 23 | return .fetchMyData 24 | case let .uploadAvatar(image): 25 | return .uploadAvatar(image) 26 | case let .updateUserName(firstName, lastName): 27 | return .updateUserName(firstName, lastName) 28 | case let .createAttachment(attacment): 29 | return .createAttachment(attacment) 30 | case let .userResponse(res): 31 | return .userResponse(res) 32 | // case let .settings(action): 33 | // return .settings(action) 34 | case let .imagePicker(action: action): 35 | return .imagePicker(action: action) 36 | case .resetAuthData: 37 | return .resetAuthData 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AddameSPM/Sources/ProfileView/ProfileState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileState.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.04.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import AddaSharedModels 10 | import SwiftUI 11 | import ImagePicker 12 | import MyEventsView 13 | 14 | // swiftlint:disable all 15 | extension Profile.State { 16 | private static var userWithoutAvatar = UserOutput.withFirstName 17 | 18 | private static var userWithAvatar = UserOutput.withAttachments 19 | 20 | public static let profileStateWithUserWithAvatar = Self( 21 | alert: nil, 22 | user: userWithAvatar, 23 | imageURLs: [ 24 | "https://adda.nyc3.digitaloceanspaces.com/uploads/images/5fabb05d2470c17919b3c0e2/1607338279849.heic", 25 | "https://adda.nyc3.digitaloceanspaces.com/uploads/images/5fabb05d2470c17919b3c0e2/1607338304864.heic", 26 | "https://adda.nyc3.digitaloceanspaces.com/uploads/images/5fabb05d2470c17919b3c0e2/1605875164101.heic", 27 | "https://adda.nyc3.digitaloceanspaces.com/uploads/images/5fabb05d2470c17919b3c0e2/1605811106589.jpeg", 28 | "https://adda.nyc3.digitaloceanspaces.com/uploads/images/5fabb05d2470c17919b3c0e2/1605796266916.jpeg", 29 | "https://adda.nyc3.digitaloceanspaces.com/uploads/images/5fabb05d2470c17919b3c0e2/5fabb05d2470c17919b3c0e2_1605792619988.jpeg" 30 | ], 31 | settingsState: .init() 32 | ) 33 | 34 | public static let profileStateWithUserWithoutAvatar = Self( 35 | alert: nil, 36 | user: userWithoutAvatar, 37 | settingsState: .init() 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /AddameSPM/Sources/RegisterFormFeature/LocationPermissionView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIHelpers 3 | import AddaSharedModels 4 | import UserDefaultsClient 5 | import ComposableArchitecture 6 | import ComposableUserNotifications 7 | 8 | struct LocationPermissionView: View { 9 | 10 | @Environment(\.colorScheme) var colorScheme 11 | let store: StoreOf 12 | 13 | var body: some View { 14 | WithViewStore(self.store, observe: { $0 }) { viewStore in 15 | VStack(alignment: .center) { 16 | Circle() 17 | .strokeBorder(Color.green, lineWidth: 5) 18 | .background( 19 | Circle() 20 | .strokeBorder(Color.yellow, lineWidth: 5) 21 | .frame(width: 220, height: 220) 22 | .background( 23 | Image(systemName: "location") 24 | .resizable() 25 | .frame(width: 100, height: 100) 26 | .foregroundColor(.backgroundColor(for: colorScheme)) 27 | ) 28 | ) 29 | .frame(width: 300, height: 300) 30 | .padding(.top, 50) 31 | 32 | Text("Get conversations notification from group you already joined.") 33 | .font(Font.system(size: 30, weight: .medium, design: .rounded)) 34 | .multilineTextAlignment(.center) 35 | .padding() 36 | 37 | Spacer() 38 | 39 | Button { 40 | viewStore.send(.isLocationEnableContinueButtonTapped, animation: .easeInOut) 41 | } label: { 42 | HStack { 43 | Text("Continue") 44 | .font(Font.system(size: 30, weight: .medium, design: .rounded)) 45 | .foregroundColor(.white) 46 | .frame(height: 10, alignment: .center) 47 | .padding(10) 48 | 49 | Image(systemName: "location") 50 | .resizable() 51 | .frame(width: 30, height: 30) 52 | .font(Font.system(size: 30, weight: .medium)) 53 | .foregroundColor(.white) 54 | .padding(.horizontal, 10) 55 | } 56 | .padding(15) 57 | } 58 | .background(Color.orange) 59 | .clipShape(Capsule()) 60 | .frame(height: 40, alignment: .center) 61 | .padding(.bottom, 50) 62 | 63 | } 64 | .tabViewStyle(.page(indexDisplayMode: .never)) 65 | } 66 | 67 | } 68 | } 69 | 70 | #if DEBUG 71 | //struct LocationPermissionView_Previews: PreviewProvider { 72 | // static var store = Store( 73 | // initialState: RegisterFormReducer.State(), 74 | // reducer: RegisterFormReducer() 75 | // ) 76 | // 77 | // static var previews: some View { 78 | // LocationPermissionView(store: store) 79 | // } 80 | //} 81 | #endif 82 | 83 | -------------------------------------------------------------------------------- /AddameSPM/Sources/RegisterFormFeature/RegisterFormView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import ComposableArchitecture 3 | import ComposableUserNotifications 4 | import UserDefaultsClient 5 | import AddaSharedModels 6 | import UserNotifications 7 | import URLRouting 8 | import APIClient 9 | import Combine 10 | import SwiftUIHelpers 11 | import LocationReducer 12 | 13 | public struct RegisterFormView: View { 14 | 15 | let store: StoreOf 16 | 17 | public init(store: StoreOf) { 18 | self.store = store 19 | } 20 | 21 | struct ViewState: Equatable { 22 | 23 | @BindingState var selectedPage: FormTag 24 | var waitingForLoginView: Bool 25 | var locationState: LocationReducer.State? 26 | 27 | public init(state: RegisterFormReducer.State) { 28 | self.selectedPage = state.selectedPage 29 | self.waitingForLoginView = state.waitingForLoginView 30 | self.locationState = state.locationState 31 | } 32 | 33 | } 34 | 35 | public var body: some View { 36 | WithViewStore(self.store, observe: ViewState.init) { viewStore in 37 | ZStack(alignment: .bottomTrailing) { 38 | TabView( 39 | selection: viewStore.binding( 40 | get: \.selectedPage, 41 | send: RegisterFormReducer.Action.selectedPage 42 | ) 43 | ) { 44 | NotificationPermissionView(store: store).tag(FormTag.notificationPermission) 45 | LocationPermissionView(store: store).tag(FormTag.locationPermission) 46 | IDFAPermissionView(store: store).tag(FormTag.IDFAPermission) 47 | } 48 | .tabViewStyle(.page(indexDisplayMode: .never)) 49 | 50 | } 51 | .onAppear { viewStore.send(.onApper) } 52 | } 53 | ._printChanges() 54 | } 55 | 56 | } 57 | 58 | #if DEBUG 59 | struct RegisterFormView_Previews: PreviewProvider { 60 | static var store = Store( 61 | initialState: RegisterFormReducer.State() 62 | ) { 63 | RegisterFormReducer() 64 | } 65 | 66 | static var previews: some View { 67 | RegisterFormView(store: store) 68 | } 69 | } 70 | #endif 71 | -------------------------------------------------------------------------------- /AddameSPM/Sources/SettingsFeature/NotificationsSettingsView.swift: -------------------------------------------------------------------------------- 1 | import ComposableArchitecture 2 | import SwiftUI 3 | import FoundationExtension 4 | 5 | struct NotificationsSettingsView: View { 6 | let store: StoreOf 7 | @ObservedObject var viewStore: ViewStoreOf 8 | 9 | init(store: StoreOf) { 10 | self.store = store 11 | self.viewStore = ViewStore(self.store, observe: { $0 }) 12 | } 13 | 14 | var body: some View { 15 | SettingsForm { 16 | SettingsRow { 17 | Toggle( 18 | "Enable notifications", isOn: self.viewStore.$enableNotifications.animation() 19 | ) 20 | .font(.system(size: 16, design: .rounded)) 21 | Text("*** Please dont turn off notification then whole function will be turn off") 22 | .font(.system(size: 13, design: .rounded)) 23 | .foregroundColor(.red) 24 | .padding(.top, -20) 25 | } 26 | } 27 | .navigationTitle("Notifications") 28 | } 29 | } 30 | 31 | public struct SettingsForm: View where Content: View { 32 | @Environment(\.colorScheme) var colorScheme 33 | let content: () -> Content 34 | 35 | public init(@ViewBuilder content: @escaping () -> Content) { 36 | self.content = content 37 | } 38 | 39 | public var body: some View { 40 | ScrollView { 41 | self.content() 42 | .font(.system(size: 15, design: .rounded)) 43 | // .toggleStyle(SwitchToggleStyle(tint: .isowordsOrange)) 44 | } 45 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /AddameSPM/Sources/SettingsFeature/SettingsNavigationLink.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SettingsNavigationLink: View where Destination: View { 4 | let destination: Destination 5 | let title: LocalizedStringKey 6 | 7 | var body: some View { 8 | SettingsRow { 9 | NavigationLink( 10 | destination: self.destination, 11 | label: { 12 | HStack { 13 | Text(self.title) 14 | .font(.system(size: 20, design: .rounded)) 15 | 16 | Spacer() 17 | Image(systemName: "arrow.right") 18 | .font(.system(size: 20, design: .rounded)) 19 | } 20 | } 21 | ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AddameSPM/Sources/SettingsFeature/SettingsRow.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct SettingsRow: View where Content: View { 4 | @Environment(\.colorScheme) var colorScheme 5 | let content: () -> Content 6 | 7 | public init(@ViewBuilder content: @escaping () -> Content) { 8 | self.content = content 9 | } 10 | 11 | public var body: some View { 12 | VStack(alignment: .leading) { 13 | self.content() 14 | .padding([.top, .bottom]) 15 | Rectangle() 16 | .fill(Color.hex(self.colorScheme == .dark ? 0x7d7d7d : 0xEEEEEE)) 17 | .frame(maxWidth: .infinity, minHeight: 2, idealHeight: 2, maxHeight: 2) 18 | } 19 | .padding(.horizontal) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AddameSPM/Sources/SettingsFeature/TermsWebView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import WebKit 3 | 4 | public struct TermsAndPrivacyWebRepresentableView: UIViewRepresentable { 5 | let urlString: String? 6 | 7 | public func makeUIView(context _: Context) -> WKWebView { 8 | return WKWebView() 9 | } 10 | 11 | public func updateUIView(_ uiView: WKWebView, context _: Context) { 12 | if let safeString = urlString, let url = URL(string: safeString) { 13 | let request = URLRequest(url: url) 14 | uiView.load(request) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AddameSPM/Sources/TabsView/TabState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabsState.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 06.04.2021. 6 | // 7 | -------------------------------------------------------------------------------- /AddameSPM/Sources/TabsView/TabsAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabAction.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 05.04.2021. 6 | // 7 | 8 | import EventView 9 | import ConversationsView 10 | import ProfileView 11 | import Foundation 12 | 13 | 14 | import SwiftUI 15 | import AddaSharedModels 16 | -------------------------------------------------------------------------------- /AddameSPM/Sources/TabsView/TabsEnvironment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabsEnvironment.swift 3 | // 4 | // 5 | // Created by Saroar Khandoker on 04.06.2021. 6 | // 7 | 8 | import Combine 9 | import ComposableArchitecture 10 | 11 | import InfoPlist 12 | import KeychainClient 13 | import AddaSharedModels 14 | 15 | import DeviceClient 16 | import Foundation 17 | 18 | // public struct TabsEnvironment { 19 | // public var backgroundQueue: AnySchedulerOf 20 | // public var mainQueue: AnySchedulerOf 21 | // public let webSocketClient: WebSocketClient 22 | // public var deviceClient: DeviceClient 23 | // 24 | // public init( 25 | // backgroundQueue: AnySchedulerOf, 26 | // mainQueue: AnySchedulerOf, 27 | // webSocketClient: WebSocketClient, 28 | // devicClient: DeviceClient 29 | // ) { 30 | // self.backgroundQueue = backgroundQueue 31 | // self.mainQueue = mainQueue 32 | // self.webSocketClient = webSocketClient 33 | // self.deviceClient = devicClient 34 | // } 35 | // 36 | // public func getAccessToken() -> AnyPublisher { 37 | // guard let token: RefreshTokenResponse = KeychainService.loadCodable(for: .token) else { 38 | // return Fail(error: HTTPRequest.HRError.missingTokenFromIOS) 39 | // .eraseToAnyPublisher() 40 | // } 41 | // 42 | // return Just(token.accessToken) 43 | // .setFailureType(to: HTTPRequest.HRError.self) 44 | // .eraseToAnyPublisher() 45 | // } 46 | // 47 | // public var currentUser: UserOutput { 48 | // guard let currentUSER: UserOutput = KeychainService.loadCodable(for: .user) else { 49 | // assertionFailure("current user is missing") 50 | // return UserOutput.withFirstName 51 | // } 52 | // 53 | // return currentUSER 54 | // } 55 | // } 56 | // 57 | // extension TabsEnvironment { 58 | // public static let live: TabsEnvironment = .init( 59 | // backgroundQueue: .main, 60 | // mainQueue: .main, 61 | // webSocketClient: .live, 62 | // devicClient: .live 63 | // ) 64 | // } 65 | -------------------------------------------------------------------------------- /AddameSPM/Sources/UserClient/Mocks.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// UserClient.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 27.01.2021. 6 | //// 7 | // 8 | // import Combine 9 | // import Foundation 10 | // import AddaSharedModels 11 | // 12 | // extension UserClient { 13 | // public static let happyPath = Self( 14 | // userMeHandler: { _ in UserOutput.withAttachments }, 15 | // update: { _ in 16 | // UserOutput.withAttachments.fullName = "HappyPath" 17 | // return UserOutput.withAttachments 18 | // }, 19 | // delete: { _ in true } 20 | // ) 21 | // 22 | // public static let failed = Self( 23 | // userMeHandler: { _ in UserOutput.withAttachments }, 24 | // update: { _ in UserOutput.withAttachments }, 25 | // delete: { _ in false } 26 | // ) 27 | // } 28 | -------------------------------------------------------------------------------- /AddameSPM/Sources/UserClient/UserClient.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AddameSPM/Sources/UserClientLive/Live.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// UserClient.swift 3 | //// 4 | //// 5 | //// Created by Saroar Khandoker on 27.01.2021. 6 | //// 7 | // 8 | // import Combine 9 | // import Foundation 10 | // import FoundationExtension 11 | // import AddaSharedModels 12 | // import UserClient 13 | // import URLRouting 14 | // 15 | // extension UserClient { 16 | // public static var live: UserClient = 17 | // .init( 18 | // userMeHandler: { id in 19 | // return try await UserClient.apiClient.decodedResponse( 20 | // for: .authEngine(.users(.user(id: id, route: .find))), 21 | // as: UserOutput.self, 22 | // decoder: .iso8601 23 | // ).value 24 | // }, 25 | // update: { userInput in 26 | // return try await UserClient.apiClient.decodedResponse( 27 | // for: .authEngine(.users(.update(input: userInput))), 28 | // as: UserOutput.self, 29 | // decoder: .iso8601 30 | // ).value 31 | // }, 32 | // delete: { id in 33 | // 34 | // return try await UserClient.apiClient.data( 35 | // for: .authEngine(.users(.user(id: id, route: .delete))) 36 | // ).response.isResponseOK() 37 | // 38 | //// return try await UserClient.apiClient.decodedResponse( 39 | //// for: .authEngine(.users(.user(id: id, route: .delete))), 40 | //// as: DeleteResponse.self 41 | //// ).value 42 | // } 43 | // ) 44 | // } 45 | // 46 | // extension URLResponse { 47 | // func isResponseOK() -> Bool { 48 | // if let httpResponse = self as? HTTPURLResponse { 49 | // return (200...299).contains(httpResponse.statusCode) 50 | // } 51 | // return false 52 | // } 53 | // } 54 | -------------------------------------------------------------------------------- /AddameSPM/Tests/AppFeatureTests/AppFeatureTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import AppFeature 4 | 5 | final class AppFeatureTests: XCTestCase { 6 | func testExample() { 7 | // This is an example of a functional test case. 8 | // Use XCTAssert and related functions to verify your tests produce the correct 9 | // results. 10 | // XCTAssertEqual(Addame().text, "Hello, World!") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AddameSPM/Tests/MapViewTests/MapViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // File 4 | // 5 | // Created by Saroar Khandoker on 01.09.2021. 6 | // 7 | 8 | import ComposableArchitecture 9 | import XCTest 10 | 11 | @testable import MapView 12 | 13 | class MapViewTests: XCTestCase { 14 | let scheduler = DispatchQueue.test 15 | } 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adda2 iOS app 2 | Adda2 is a free, open source, messaging app for simple private communication with friends. 3 | 4 | [![Available on the App Store](http://cl.ly/WouG/Download_on_the_App_Store_Badge_US-UK_135x40.svg)](https://apps.apple.com/app/id1538487173) 5 | 6 | ## Technical overview 7 | - [The Composable Architecture (TCA)](https://github.com/pointfreeco/swift-composable-architecture) 8 | - Swift Package Manager 9 | - Unit tests 10 | - UI tests 11 | - Modularisation 12 | 13 | 14 | This is a project demonstrating the capabilities of [The Composable Architecture (TCA)](https://github.com/pointfreeco/swift-composable-architecture) and Swift Package Manager. 15 | TCA allows developers to fully encapsulate state, actions and environment to control its side effects. 16 | 17 | This allows for easier dependency management where we can have more control of what goes where when needed. 18 | 19 | Compared to other ways of building and developing applications, TCA allows for building new **Features** in parallel in a big team. 20 | Productivity increases while cognitive load stays at a manageable level. 21 | 22 | ### Use Adda to: 23 | * Discover users who live nearby 24 | * You can see nearby stories anonymously 25 | * You can see any place on the map to explore new friends and events 26 | * Locate content anywhere on the map 27 | * Build your own local social network 28 | * Be social, Be Friendly 29 | 30 | Adda2, a new way location-based network to meet new people and know more about them. With real-time events, hangouts, and communication. You can post what you want to do, nearby so people can see: Grabbing a taxi, selling, buying, events around you, hangouts or helping out those near you are some examples.​ We believe with this app you can meet people in real life much easier. Our goal is to let people connect in real life as opposed to just connecting on your screen. 31 | 32 | Meeting new neighbors​ is easier than ever. 33 | 34 | ## Requirements 35 | 36 | ### Build 37 | - Xcode 12 38 | - SwiftUI 100% 39 | - Swift 5 40 | 41 | ### Deployment target 42 | - iOS 14.0 43 | 44 | ### Privacy - Contacts Usage Description 45 | Importent Note: Adda2 uses your contacts to find users you konw. We do not store your contacts on the server. 46 | code here: https://github.com/AddaMeSPB/AddaMeAuth/blob/master/Sources/App/Controllers/ContactController.swift#L21 47 | 48 | 49 | ### SwiftUI Test for create screenshot with fastlane 50 | [YouTube Link](https://youtu.be/A_Xvjs6frCQ) 51 | 52 | ![SwiftUI Test](https://user-images.githubusercontent.com/8770772/102008996-91051800-3d45-11eb-8bd0-1fd05d7acfbc.gif) 53 | -------------------------------------------------------------------------------- /fastlane/Appfile.swift: -------------------------------------------------------------------------------- 1 | // The bundle identifier of your app 2 | var appIdentifier: String { return "com.addame.AddaMeIOS" } 3 | // Your Apple email address 4 | var appleID: String { return "saroarkhandoker@yahoo.com" } 5 | 6 | // App Store Connect Team ID 7 | var itcTeam: String? { return "119123163" } 8 | // Apple Developer Portal Team ID 9 | var teamID: String { return "6989658CU5" } 10 | 11 | var appVersion: String { return "1.0.0" } 12 | // For more information about the Appfile, see: 13 | // https://docs.fastlane.tools/advanced/#appfile 14 | -------------------------------------------------------------------------------- /fastlane/metadata/copyright.txt: -------------------------------------------------------------------------------- 1 | 2020 Saroar Khandoker 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/apple_tv_privacy_policy.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/description.txt: -------------------------------------------------------------------------------- 1 | Use Adda to: 2 | -> Discover users who live nearby 3 | -> You can see nearby stories anonymously 4 | -> You can see any place on the map to explore new friends and events 5 | -> Locate content anywhere on the map 6 | -> Build your own local social network 7 | -> Be social, Be Friendly 8 | 9 | ADDA, a new way location-based network to meet new people and know more about them. With real-time events, hangouts, and communication. You can post what you want to do, nearby so people can see: Grabbing a taxi, selling, buying, events around you, hangouts, or helping out those near you are some examples.​ We believe with this app you can meet people in real life much easier. Our goal is to let people connect in real life as opposed to just connecting on your screen. 10 | 11 | Meeting new neighbors​ is easier than ever. 12 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/keywords.txt: -------------------------------------------------------------------------------- 1 | walk,nearby now, Area news,neighbours,Новости района,друг,соседи,隣人,гулять,আড্ডা,会う,私の周り 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/marketing_url.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/name.txt: -------------------------------------------------------------------------------- 1 | Adda2 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/privacy_url.txt: -------------------------------------------------------------------------------- 1 | https://addame.com/v1/privacy 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/promotional_text.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/release_notes.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/subtitle.txt: -------------------------------------------------------------------------------- 1 | Adda 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/support_url.txt: -------------------------------------------------------------------------------- 1 | https://addame.com/v1/terms 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_category.txt: -------------------------------------------------------------------------------- 1 | SOCIAL_NETWORKING 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/demo_password.txt: -------------------------------------------------------------------------------- 1 | will get password via sms 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/demo_user.txt: -------------------------------------------------------------------------------- 1 | you mobile number 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/email_address.txt: -------------------------------------------------------------------------------- 1 | saroar9@gmail.com 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/first_name.txt: -------------------------------------------------------------------------------- 1 | Saroar 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/last_name.txt: -------------------------------------------------------------------------------- 1 | Khandoker 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/notes.txt: -------------------------------------------------------------------------------- 1 | Mobile auth login with a one-time password 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/phone_number.txt: -------------------------------------------------------------------------------- 1 | +79218821217 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_category.txt: -------------------------------------------------------------------------------- 1 | TRAVEL 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/screenshots/Framefile.json: -------------------------------------------------------------------------------- 1 | { 2 | "device_frame_version": "latest", 3 | "default": { 4 | "keyword": { 5 | "font": "./fonts/Chalkduster.ttf" 6 | }, 7 | "title": { 8 | "color": "#FFFFFF" 9 | }, 10 | "padding": 50, 11 | "title_below_image": false, 12 | "background": "./background.jpg", 13 | "show_complete_frame": true 14 | }, 15 | "data": [] 16 | } 17 | -------------------------------------------------------------------------------- /fastlane/screenshots/README.txt: -------------------------------------------------------------------------------- 1 | Put all screenshots you want to use inside the folder of its language (e.g. en-US). 2 | The device type will automatically be recognized using the image resolution. Apple TV screenshots 3 | should be stored in a subdirectory named appleTV with language folders inside of it. iMessage 4 | screenshots, like Apple TV screenshots, should also be stored in a subdirectory named iMessage 5 | with language folders inside of it. 6 | 7 | The screenshots can be named whatever you want, but keep in mind they are sorted alphabetically. 8 | -------------------------------------------------------------------------------- /fastlane/screenshots/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/fastlane/screenshots/background.jpg -------------------------------------------------------------------------------- /fastlane/screenshots/en-US/title.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/fastlane/screenshots/en-US/title.strings -------------------------------------------------------------------------------- /fastlane/screenshots/fonts/Chalkduster.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AddaMeSPB/AddaMeIOS/a407cf9eb7f9969cd5ca042d48c28736fc648b0c/fastlane/screenshots/fonts/Chalkduster.ttf -------------------------------------------------------------------------------- /fastlane/swift/Actions.swift: -------------------------------------------------------------------------------- 1 | // Actions.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This autogenerated file will be overwritten or replaced when running "fastlane generate_swift" 5 | // 6 | // ** NOTE ** 7 | // This file is provided by fastlane and WILL be overwritten in future updates 8 | // If you want to add extra functionality to this project, create a new file in a 9 | // new group so that it won't be marked for upgrade 10 | // 11 | 12 | import Foundation 13 | 14 | // Please don't remove the lines below 15 | // They are used to detect outdated files 16 | // FastlaneRunnerAPIVersion [0.9.56] 17 | -------------------------------------------------------------------------------- /fastlane/swift/Appfile.swift: -------------------------------------------------------------------------------- 1 | // Appfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // The bundle identifier of your app 5 | var appIdentifier: String { return "" } 6 | // Your Apple email address 7 | var appleID: String { return "" } 8 | 9 | // Developer Portal Team ID 10 | var teamID: String { return "" } 11 | var itcTeam: String? { return nil } // App Store Connect Team ID (may be nil if no team) 12 | 13 | // you can even provide different app identifiers, Apple IDs and team names per lane: 14 | // More information: https://docs.fastlane.tools/advanced/#appfile 15 | 16 | // Please don't remove the lines below 17 | // They are used to detect outdated files 18 | // FastlaneRunnerAPIVersion [0.9.1] 19 | -------------------------------------------------------------------------------- /fastlane/swift/ControlCommand.swift: -------------------------------------------------------------------------------- 1 | // ControlCommand.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | struct ControlCommand: RubyCommandable { 14 | static let commandKey = "command" 15 | var type: CommandType { return .control } 16 | 17 | enum ShutdownCommandType { 18 | static let userMessageKey: String = "userMessage" 19 | 20 | enum CancelReason { 21 | static let reasonKey: String = "reason" 22 | case clientError 23 | case serverError 24 | 25 | var reasonText: String { 26 | switch self { 27 | case .clientError: 28 | return "clientError" 29 | case .serverError: 30 | return "serverError" 31 | } 32 | } 33 | } 34 | 35 | case done 36 | case cancel(cancelReason: CancelReason) 37 | 38 | var token: String { 39 | switch self { 40 | case .done: 41 | return "done" 42 | case .cancel: 43 | return "cancelFastlaneRun" 44 | } 45 | } 46 | } 47 | 48 | let message: String? 49 | let id: String = UUID().uuidString 50 | let shutdownCommandType: ShutdownCommandType 51 | var commandJson: String { 52 | var jsonDictionary: [String: Any] = [ControlCommand.commandKey: shutdownCommandType.token] 53 | 54 | if let message = message { 55 | jsonDictionary[ShutdownCommandType.userMessageKey] = message 56 | } 57 | if case let .cancel(reason) = shutdownCommandType { 58 | jsonDictionary[ShutdownCommandType.CancelReason.reasonKey] = reason.reasonText 59 | } 60 | 61 | let jsonData = try! JSONSerialization.data(withJSONObject: jsonDictionary, options: []) 62 | let jsonString = String(data: jsonData, encoding: .utf8)! 63 | return jsonString 64 | } 65 | 66 | init(commandType: ShutdownCommandType, message: String? = nil) { 67 | shutdownCommandType = commandType 68 | self.message = message 69 | } 70 | } 71 | 72 | // Please don't remove the lines below 73 | // They are used to detect outdated files 74 | // FastlaneRunnerAPIVersion [0.9.2] 75 | -------------------------------------------------------------------------------- /fastlane/swift/Deliverfile.swift: -------------------------------------------------------------------------------- 1 | // Deliverfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `deliver` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Deliverfile: DeliverfileProtocol { 15 | // If you want to enable `deliver`, run `fastlane deliver init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | public let username = "saroarkhandoker@yahoo.com" 19 | public let appIdentifier = "com.addame.AddaMeIOS" 20 | public let languages: [String]? = ["en-US"] 21 | public let screenshotsPath: String? = "./fastlane/screenshots" 22 | } 23 | 24 | // Generated with fastlane 2.193.1 25 | -------------------------------------------------------------------------------- /fastlane/swift/Fastfile.swift: -------------------------------------------------------------------------------- 1 | // This class is automatically included in FastlaneRunner during build 2 | // If you have a custom Fastfile.swift, this file will be replaced by it 3 | // Don't modify this file unless you are familiar with how fastlane's swift code generation works 4 | // *** This file will be overwritten or replaced during build time *** 5 | 6 | import Foundation 7 | 8 | open class Fastfile: LaneFile { 9 | override public init() { 10 | super.init() 11 | } 12 | } 13 | 14 | // Please don't remove the lines below 15 | // They are used to detect outdated files 16 | // FastlaneRunnerAPIVersion [0.9.1] 17 | -------------------------------------------------------------------------------- /fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /fastlane/swift/FastlaneSwiftRunner/README.txt: -------------------------------------------------------------------------------- 1 | Don't modify the structure of this group including but not limited to: 2 | - renaming this group 3 | - adding sub groups 4 | - removing sub groups 5 | - adding new files 6 | - removing files 7 | 8 | If you modify anything in this folder, future fastlane upgrades may not be able to be applied automatically. 9 | 10 | If you need to add new groups, please add them at the root of the "Fastlane Runner" group. 11 | -------------------------------------------------------------------------------- /fastlane/swift/Gymfile.swift: -------------------------------------------------------------------------------- 1 | // Gymfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `gym` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Gymfile: GymfileProtocol { 15 | // If you want to enable `gym`, run `fastlane gym init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.193.1 21 | -------------------------------------------------------------------------------- /fastlane/swift/MainProcess.swift: -------------------------------------------------------------------------------- 1 | // MainProcess.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | #if canImport(SwiftShell) 14 | import SwiftShell 15 | #endif 16 | 17 | let argumentProcessor = ArgumentProcessor(args: CommandLine.arguments) 18 | let timeout = argumentProcessor.commandTimeout 19 | 20 | class MainProcess { 21 | var doneRunningLane = false 22 | var thread: Thread! 23 | #if SWIFT_PACKAGE 24 | var lastPrintDate = Date.distantFuture 25 | var timeBetweenPrints = Int.min 26 | var rubySocketCommand: AsyncCommand! 27 | #endif 28 | 29 | @objc func connectToFastlaneAndRunLane(_ fastfile: LaneFile?) { 30 | runner.startSocketThread(port: argumentProcessor.port) 31 | 32 | let completedRun = Fastfile.runLane(from: fastfile, named: argumentProcessor.currentLane, with: argumentProcessor.laneParameters()) 33 | if completedRun { 34 | runner.disconnectFromFastlaneProcess() 35 | } 36 | 37 | doneRunningLane = true 38 | } 39 | 40 | func startFastlaneThread(with fastFile: LaneFile?) { 41 | #if !SWIFT_PACKAGE 42 | thread = Thread(target: self, selector: #selector(connectToFastlaneAndRunLane), object: nil) 43 | #else 44 | thread = Thread(target: self, selector: #selector(connectToFastlaneAndRunLane), object: fastFile) 45 | #endif 46 | thread.name = "worker thread" 47 | #if SWIFT_PACKAGE 48 | let PATH = run("/bin/bash", "-c", "-l", "eval $(/usr/libexec/path_helper -s) ; echo $PATH").stdout 49 | main.env["PATH"] = PATH 50 | let path = main.run(bash: "which fastlane").stdout 51 | let pids = main.run("lsof", "-t", "-i", ":2000").stdout.split(separator: "\n") 52 | pids.forEach { main.run("kill", "-9", $0) } 53 | rubySocketCommand = main.runAsync(path, "socket_server", "-c", "1200") 54 | lastPrintDate = Date() 55 | rubySocketCommand.stderror.onStringOutput { print($0) } 56 | rubySocketCommand.stdout.onStringOutput { stdout in 57 | print(stdout) 58 | self.timeBetweenPrints = Int(self.lastPrintDate.timeIntervalSinceNow) 59 | } 60 | 61 | // swiftformat:disable:next redundantSelf 62 | _ = Runner.waitWithPolling(self.timeBetweenPrints, toEventually: { $0 > 5 }, timeout: 10) 63 | thread.start() 64 | #endif 65 | } 66 | } 67 | 68 | public class Main { 69 | let process = MainProcess() 70 | 71 | public init() {} 72 | 73 | public func run(with fastFile: LaneFile?) { 74 | process.startFastlaneThread(with: fastFile) 75 | 76 | while !process.doneRunningLane, RunLoop.current.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 2)) { 77 | // no op 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /fastlane/swift/Matchfile.swift: -------------------------------------------------------------------------------- 1 | // Matchfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `match` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Matchfile: MatchfileProtocol { 15 | // If you want to enable `match`, run `fastlane match init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.193.1 21 | -------------------------------------------------------------------------------- /fastlane/swift/Plugins.swift: -------------------------------------------------------------------------------- 1 | // Plugins.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This autogenerated file will be overwritten or replaced when installing/updating plugins or running "fastlane generate_swift" 5 | // 6 | // ** NOTE ** 7 | // This file is provided by fastlane and WILL be overwritten in future updates 8 | // If you want to add extra functionality to this project, create a new file in a 9 | // new group so that it won't be marked for upgrade 10 | // 11 | 12 | import Foundation 13 | 14 | // Please don't remove the lines below 15 | // They are used to detect outdated files 16 | // FastlaneRunnerAPIVersion [0.9.56] 17 | -------------------------------------------------------------------------------- /fastlane/swift/Precheckfile.swift: -------------------------------------------------------------------------------- 1 | // Precheckfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `precheck` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Precheckfile: PrecheckfileProtocol { 15 | // If you want to enable `precheck`, run `fastlane precheck init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.193.1 21 | -------------------------------------------------------------------------------- /fastlane/swift/PrecheckfileProtocol.swift: -------------------------------------------------------------------------------- 1 | // PrecheckfileProtocol.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | public protocol PrecheckfileProtocol: class { 5 | /// Path to your App Store Connect API Key JSON file (https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file) 6 | var apiKeyPath: String? { get } 7 | 8 | /// Your App Store Connect API Key information (https://docs.fastlane.tools/app-store-connect-api/#use-return-value-and-pass-in-as-an-option) 9 | var apiKey: [String: Any]? { get } 10 | 11 | /// The bundle identifier of your app 12 | var appIdentifier: String { get } 13 | 14 | /// Your Apple ID Username 15 | var username: String? { get } 16 | 17 | /// The ID of your App Store Connect team if you're in multiple teams 18 | var teamId: String? { get } 19 | 20 | /// The name of your App Store Connect team if you're in multiple teams 21 | var teamName: String? { get } 22 | 23 | /// The platform to use (optional) 24 | var platform: String { get } 25 | 26 | /// The default rule level unless otherwise configured 27 | var defaultRuleLevel: String { get } 28 | 29 | /// Should check in-app purchases? 30 | var includeInAppPurchases: Bool { get } 31 | 32 | /// Should force check live app? 33 | var useLive: Bool { get } 34 | 35 | /// using text indicating that your IAP is free 36 | var freeStuffInIap: String? { get } 37 | } 38 | 39 | public extension PrecheckfileProtocol { 40 | var apiKeyPath: String? { return nil } 41 | var apiKey: [String: Any]? { return nil } 42 | var appIdentifier: String { return "" } 43 | var username: String? { return nil } 44 | var teamId: String? { return nil } 45 | var teamName: String? { return nil } 46 | var platform: String { return "ios" } 47 | var defaultRuleLevel: String { return "error" } 48 | var includeInAppPurchases: Bool { return true } 49 | var useLive: Bool { return false } 50 | var freeStuffInIap: String? { return nil } 51 | } 52 | 53 | // Please don't remove the lines below 54 | // They are used to detect outdated files 55 | // FastlaneRunnerAPIVersion [0.9.78] 56 | -------------------------------------------------------------------------------- /fastlane/swift/RubyCommandable.swift: -------------------------------------------------------------------------------- 1 | // RubyCommandable.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | enum CommandType { 14 | case action 15 | case control 16 | 17 | var token: String { 18 | switch self { 19 | case .action: 20 | return "action" 21 | case .control: 22 | return "control" 23 | } 24 | } 25 | } 26 | 27 | protocol RubyCommandable { 28 | var type: CommandType { get } 29 | var commandJson: String { get } 30 | var id: String { get } 31 | } 32 | 33 | extension RubyCommandable { 34 | var json: String { 35 | return """ 36 | { "commandType": "\(type.token)", "command": \(commandJson) } 37 | """ 38 | } 39 | } 40 | 41 | // Please don't remove the lines below 42 | // They are used to detect outdated files 43 | // FastlaneRunnerAPIVersion [0.9.2] 44 | -------------------------------------------------------------------------------- /fastlane/swift/RunnerArgument.swift: -------------------------------------------------------------------------------- 1 | // RunnerArgument.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | struct RunnerArgument { 14 | let name: String 15 | let value: String 16 | } 17 | 18 | // Please don't remove the lines below 19 | // They are used to detect outdated files 20 | // FastlaneRunnerAPIVersion [0.9.2] 21 | -------------------------------------------------------------------------------- /fastlane/swift/Scanfile.swift: -------------------------------------------------------------------------------- 1 | // Scanfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | import CoreGraphics 5 | 6 | // This class is automatically included in FastlaneRunner during build 7 | 8 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `scan` 9 | // 10 | // ** NOTE ** 11 | // This file is provided by fastlane and WILL be overwritten in future updates 12 | // If you want to add extra functionality to this project, create a new file in a 13 | // new group so that it won't be marked for upgrade 14 | // 15 | 16 | public class Scanfile: ScanfileProtocol { 17 | // If you want to enable `scan`, run `fastlane scan init` 18 | // After, this file will be replaced with a custom implementation that contains values you supplied 19 | // during the `init` process, and you won't see this message 20 | 21 | public var devices: [String]? = [ 22 | "iPhone 12 Pro" 23 | // "iPhone 8 Plus", 24 | // "iPhone Xs Max", 25 | // "iPhone 12 Pro", 26 | // "iPad Pro (12.9-inch) (2nd generation)", 27 | // "iPad Pro (12.9-inch) (3rd generation)" 28 | ] 29 | 30 | public var scheme: String? = "AddaMeIOSUITests" 31 | 32 | public var failBuild: Bool = true 33 | 34 | public var resetSimulator: Bool = false 35 | 36 | public var clean: Bool = true 37 | 38 | public var reinstallApp: Bool = true 39 | 40 | } 41 | 42 | // Generated with fastlane 2.193.1 43 | -------------------------------------------------------------------------------- /fastlane/swift/Screengrabfile.swift: -------------------------------------------------------------------------------- 1 | // Screengrabfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `screengrab` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Screengrabfile: ScreengrabfileProtocol { 15 | // If you want to enable `screengrab`, run `fastlane screengrab init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.193.1 21 | -------------------------------------------------------------------------------- /fastlane/swift/Snapshotfile.swift: -------------------------------------------------------------------------------- 1 | // Snapshotfile.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `snapshot` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Snapshotfile: SnapshotfileProtocol { 15 | // If you want to enable `snapshot`, run `fastlane snapshot init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.193.1 21 | -------------------------------------------------------------------------------- /fastlane/swift/SocketClientDelegateProtocol.swift: -------------------------------------------------------------------------------- 1 | // SocketClientDelegateProtocol.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | protocol SocketClientDelegateProtocol: class { 14 | func connectionsOpened() 15 | func connectionsClosed() 16 | func commandExecuted(serverResponse: SocketClientResponse, completion: (SocketClient) -> Void) 17 | } 18 | 19 | // Please don't remove the lines below 20 | // They are used to detect outdated files 21 | // FastlaneRunnerAPIVersion [0.9.2] 22 | -------------------------------------------------------------------------------- /fastlane/swift/formatting/Brewfile: -------------------------------------------------------------------------------- 1 | brew("swiftformat") 2 | -------------------------------------------------------------------------------- /fastlane/swift/formatting/Brewfile.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "entries": { 3 | "brew": { 4 | "swiftformat": { 5 | "version": "0.48.11", 6 | "bottle": { 7 | "rebuild": 0, 8 | "root_url": "https://ghcr.io/v2/homebrew/core", 9 | "files": { 10 | "arm64_big_sur": { 11 | "cellar": ":any_skip_relocation", 12 | "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:e0a851cfa2ff5d04f0fc98a9e624d1411f1b5b1e55e3cbc0901f4913c02e716a", 13 | "sha256": "e0a851cfa2ff5d04f0fc98a9e624d1411f1b5b1e55e3cbc0901f4913c02e716a" 14 | }, 15 | "big_sur": { 16 | "cellar": ":any_skip_relocation", 17 | "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:a5327283fe32b2ef2c6f264e14c966a9a60cb291415d3d05ed659c92a93c4987", 18 | "sha256": "a5327283fe32b2ef2c6f264e14c966a9a60cb291415d3d05ed659c92a93c4987" 19 | }, 20 | "catalina": { 21 | "cellar": ":any_skip_relocation", 22 | "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:ba95e49ecc71bb19734698dee565e3b0ced6470729206cb434675cfa051f2755", 23 | "sha256": "ba95e49ecc71bb19734698dee565e3b0ced6470729206cb434675cfa051f2755" 24 | }, 25 | "mojave": { 26 | "cellar": ":any_skip_relocation", 27 | "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:c7e00eae9d46dddf040999f0f2832d08110f093c7a403aaaaaa18d8830213967", 28 | "sha256": "c7e00eae9d46dddf040999f0f2832d08110f093c7a403aaaaaa18d8830213967" 29 | } 30 | } 31 | } 32 | } 33 | } 34 | }, 35 | "system": { 36 | "macos": { 37 | "catalina": { 38 | "HOMEBREW_VERSION": "3.2.0-77-gd305f72", 39 | "HOMEBREW_PREFIX": "/usr/local", 40 | "Homebrew/homebrew-core": "0b13b342053d414d1b241c2c7a446b74d79cc90e", 41 | "CLT": "11.0.0.33.12", 42 | "Xcode": "12.4", 43 | "macOS": "10.15.7" 44 | }, 45 | "big_sur": { 46 | "HOMEBREW_VERSION": "3.2.10-50-ge3f851d", 47 | "HOMEBREW_PREFIX": "/opt/homebrew", 48 | "Homebrew/homebrew-core": "73588fb5f5edccfe62f1b290a3298b402fbd71d5", 49 | "CLT": "12.5.1.0.1.1623191612", 50 | "Xcode": "13.0", 51 | "macOS": "11.5.2" 52 | }, 53 | "monterey": { 54 | "HOMEBREW_VERSION": "3.2.6-34-g6bb3699", 55 | "HOMEBREW_PREFIX": "/usr/local", 56 | "Homebrew/homebrew-core": "b7523de28df0f0f819ff2c49c84611eec19f5455", 57 | "CLT": "13.0.0.0.1.1626155413", 58 | "Xcode": "13.0", 59 | "macOS": "12.0" 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /fastlane/swift/formatting/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | task(default: %w[setup]) 4 | 5 | task(setup: [:brew, :lint]) 6 | 7 | task(:brew) do 8 | raise '`brew` is required. Please install brew. https://brew.sh/' unless system('which brew') 9 | 10 | puts('➡️ Brew') 11 | sh('brew bundle') 12 | end 13 | 14 | task(:lint) do 15 | Dir.chdir('..') do 16 | sh("swiftformat . --config formatting/.swiftformat --verbose --selfrequired waitWithPolling --exclude Fastfile.swift --swiftversion 4.0") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /fastlane/swift/main.swift: -------------------------------------------------------------------------------- 1 | // main.swift 2 | // Copyright (c) 2021 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | let argumentProcessor = ArgumentProcessor(args: CommandLine.arguments) 14 | let timeout = argumentProcessor.commandTimeout 15 | 16 | class MainProcess { 17 | var doneRunningLane = false 18 | var thread: Thread! 19 | 20 | @objc func connectToFastlaneAndRunLane() { 21 | runner.startSocketThread(port: argumentProcessor.port) 22 | 23 | let completedRun = Fastfile.runLane(from: nil, named: argumentProcessor.currentLane, with: argumentProcessor.laneParameters()) 24 | if completedRun { 25 | runner.disconnectFromFastlaneProcess() 26 | } 27 | 28 | doneRunningLane = true 29 | } 30 | 31 | func startFastlaneThread() { 32 | thread = Thread(target: self, selector: #selector(connectToFastlaneAndRunLane), object: nil) 33 | thread.name = "worker thread" 34 | thread.start() 35 | } 36 | } 37 | 38 | let process = MainProcess() 39 | process.startFastlaneThread() 40 | 41 | while !process.doneRunningLane, RunLoop.current.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 2)) { 42 | // no op 43 | } 44 | 45 | // Please don't remove the lines below 46 | // They are used to detect outdated files 47 | // FastlaneRunnerAPIVersion [0.9.2] 48 | -------------------------------------------------------------------------------- /fastlane/swift/upgrade_manifest.json: -------------------------------------------------------------------------------- 1 | {"Actions.swift":"Autogenerated API","Fastlane.swift":"Autogenerated API","DeliverfileProtocol.swift":"Autogenerated API","GymfileProtocol.swift":"Autogenerated API","MatchfileProtocol.swift":"Autogenerated API","Plugins.swift":"Autogenerated API","PrecheckfileProtocol.swift":"Autogenerated API","ScanfileProtocol.swift":"Autogenerated API","ScreengrabfileProtocol.swift":"Autogenerated API","SnapshotfileProtocol.swift":"Autogenerated API","LaneFileProtocol.swift":"Fastfile Components","OptionalConfigValue.swift":"Fastfile Components","ControlCommand.swift":"Networking","RubyCommand.swift":"Networking","RubyCommandable.swift":"Networking","Runner.swift":"Networking","SocketClient.swift":"Networking","SocketClientDelegateProtocol.swift":"Networking","SocketResponse.swift":"Networking","main.swift":"Runner Code","ArgumentProcessor.swift":"Runner Code","RunnerArgument.swift":"Runner Code"} --------------------------------------------------------------------------------