From 755cfbe3fae170b875fe7fdbf4bacb68e3b23d8b Mon Sep 17 00:00:00 2001 From: shingha Date: Mon, 9 May 2022 10:45:47 +0900 Subject: [PATCH 01/57] =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EC=85=8B=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- Starbucks/.swiftlint.yml | 273 +++++++++++ Starbucks/Podfile | 11 + Starbucks/Starbucks.xcodeproj/project.pbxproj | 451 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + Starbucks/Starbucks/Configuration/Info.plist | 25 + Starbucks/Starbucks/Info.plist | 5 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 93 ++++ .../Resource/Assets.xcassets/Contents.json | 6 + Starbucks/Starbucks/Sources/AppDelegate.swift | 16 + .../Starbucks/Sources/SceneDelegate.swift | 19 + .../Starbucks/Sources/ViewController.swift | 19 + 16 files changed, 966 insertions(+), 1 deletion(-) create mode 100644 Starbucks/.swiftlint.yml create mode 100644 Starbucks/Podfile create mode 100644 Starbucks/Starbucks.xcodeproj/project.pbxproj create mode 100644 Starbucks/Starbucks.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Starbucks/Starbucks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Starbucks/Starbucks.xcworkspace/contents.xcworkspacedata create mode 100644 Starbucks/Starbucks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Starbucks/Starbucks/Configuration/Info.plist create mode 100644 Starbucks/Starbucks/Info.plist create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/Contents.json create mode 100644 Starbucks/Starbucks/Sources/AppDelegate.swift create mode 100644 Starbucks/Starbucks/Sources/SceneDelegate.swift create mode 100644 Starbucks/Starbucks/Sources/ViewController.swift diff --git a/.gitignore b/.gitignore index 330d167..a5e66c3 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,7 @@ playground.xcworkspace # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # -# Pods/ + # # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace @@ -88,3 +88,6 @@ fastlane/test_output # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ + +Starbucks/Pods/ +Starbucks/Podfile.lock \ No newline at end of file diff --git a/Starbucks/.swiftlint.yml b/Starbucks/.swiftlint.yml new file mode 100644 index 0000000..dc0e77a --- /dev/null +++ b/Starbucks/.swiftlint.yml @@ -0,0 +1,273 @@ +only_rules: + +# 상시 활성화 (자동) + - anyobject_protocol + - array_init + - attributes + - block_based_kvo + - class_delegate_protocol + - closing_brace + - closure_body_length + - closure_end_indentation + - closure_parameter_position + - closure_spacing + - collection_alignment + - colon + - comma + - compiler_protocol_init +# - conditional_returns_on_newline + - contains_over_filter_count + - contains_over_filter_is_empty + - contains_over_first_not_nil + - contains_over_range_nil_comparison + - control_statement + - convenience_type + - custom_rules + - cyclomatic_complexity + - deployment_target + - discarded_notification_center_observer + - discouraged_direct_init +# - discouraged_object_literal +# - discouraged_optional_boolean +# - discouraged_optional_collection + - duplicate_enum_cases + - duplicate_imports + - dynamic_inline + - empty_collection_literal + - empty_count + - empty_enum_arguments + - empty_parameters + - empty_parentheses_with_trailing_closure + - empty_string + - empty_xctest_method + - enum_case_associated_values_count +# - explicit_acl +# - explicit_enum_raw_value + - explicit_init +# - explicit_self +# - explicit_top_level_acl +# - explicit_type_interface + - extension_access_modifier + - fallthrough + - fatal_error_message +# - file_header + - file_length +# - file_name + - file_name_no_space +# - file_types_order + - first_where + - flatmap_over_map_reduce + - for_where + - force_cast + - force_try + - force_unwrapping +# - function_body_length +# - function_default_parameter_at_end + - function_parameter_count + - generic_type_name + - identical_operands +# - identifier_name + - implicit_getter + - implicit_return +# - implicitly_unwrapped_optional +# - indentation_width + - inert_defer + - is_disjoint + - joined_default_parameter + - large_tuple + - last_where + - leading_whitespace + - legacy_cggeometry_functions + - legacy_constant + - legacy_constructor + - legacy_hashing + - legacy_multiple + - legacy_nsgeometry_functions + - legacy_random + - let_var_whitespace + - line_length + - literal_expression_end_indentation + - lower_acl_than_parent + - mark +# - missing_docs + - modifier_order + - multiline_arguments +# - multiline_arguments_brackets +# - multiline_function_chains + - multiline_literal_brackets + - multiline_parameters +# - multiline_parameters_brackets +# - multiple_closures_with_trailing_closure +# - nesting + - nimble_operator +# - no_extension_access_modifier + - no_fallthrough_only +# - no_grouping_extension + - no_space_in_method_call + - notification_center_detachment +# - nslocalizedstring_key +# - nslocalizedstring_require_bundle + - nsobject_prefer_isequal +# - number_separator +# - object_literal + - opening_brace + - operator_usage_whitespace + - operator_whitespace + - optional_enum_case_matching +# - orphaned_doc_comment + - overridden_super_call + - override_in_extension +# - pattern_matching_keywords + - prefer_self_type_over_type_of_self +# - prefixed_toplevel_constant + - private_action +# - private_outlet + - private_over_fileprivate + - private_unit_test +# - prohibited_interface_builder + - prohibited_super_call + - protocol_property_accessors_order +# - quick_discouraged_call +# - quick_discouraged_focused_test +# - quick_discouraged_pending_test + - raw_value_for_camel_cased_codable_enum + - reduce_boolean + - reduce_into + - redundant_discardable_let + - redundant_nil_coalescing + - redundant_objc_attribute + - redundant_optional_initialization + - redundant_set_access_control + - redundant_string_enum_value + - redundant_type_annotation + - redundant_void_return +# - required_deinit + - required_enum_case + - return_arrow_whitespace + - shorthand_operator + - single_test_class + - sorted_first_last + - sorted_imports + - statement_position + - static_operator +# - strict_fileprivate +# - strong_iboutlet +# - superfluous_disable_command + - switch_case_alignment +# - switch_case_on_newline + - syntactic_sugar +# - todo + - toggle_bool +# - trailing_closure + - trailing_comma + - trailing_newline + - trailing_semicolon +# - trailing_whitespace +# - type_body_length +# - type_contents_order + - type_name + - unavailable_function + - unneeded_break_in_switch + - unneeded_parentheses_in_closure_argument +# - unowned_variable_capture + - untyped_error_in_catch + - unused_capture_list + - unused_closure_parameter + - unused_control_flow_label + - unused_declaration + - unused_enumerated + - unused_import + - unused_optional_binding +# - unused_setter_value + - valid_ibinspectable + - vertical_parameter_alignment + - vertical_parameter_alignment_on_call + - vertical_whitespace +# - vertical_whitespace_between_cases + - vertical_whitespace_closing_braces +# - vertical_whitespace_opening_braces + - void_return + - weak_delegate + - xct_specific_matcher + - xctfail_message + - yoda_condition + + +# 검사할 파일경로 +included: + - Starbucks/Sources + +# 제외할 파일경로 (included보다 우선순위 높음) +excluded: + - Pods + +# 룰별 커스터마이징 +line_length: + ignores_urls: true + warning: 300 + error: 400 + +closure_body_length: + warning: 80 + error: 100 + +cyclomatic_complexity: + warning: 30 + error: 30 + +large_tuple: + warning: 4 + +type_name: + min_length: 2 + max_length: 60 + warning: 50 + error: 60 + +function_parameter_count: + warning: 7 + +file_length: + warning: 1000 + +force_cast: + severity: error + +force_unwrapping: + severity: error + +class_delegate_protocol: + severity: error + +closure_spacing: + severity: error + +compiler_protocol_init: + severity: error + +control_statement: + severity: error + +implicit_getter: + severity: error + +operator_whitespace: + severity: error + +redundant_string_enum_value: + severity: error + +syntactic_sugar: + severity: error + +unused_enumerated: + severity: error + +unused_optional_binding: + severity: error + +vertical_parameter_alignment: + severity: error + +weak_delegate: + severity: error diff --git a/Starbucks/Podfile b/Starbucks/Podfile new file mode 100644 index 0000000..ec86187 --- /dev/null +++ b/Starbucks/Podfile @@ -0,0 +1,11 @@ +target 'Starbucks' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for sidedish + + pod 'SwiftLint' + pod 'FirebaseAuth' + pod 'GoogleSignIn' + pod 'SnapKit', '~> 5.6.0' +end diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2745c5e --- /dev/null +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -0,0 +1,451 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; + E01B527A28289D17009918AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527928289D17009918AE /* AppDelegate.swift */; }; + E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527B28289D17009918AE /* SceneDelegate.swift */; }; + E01B527E28289D17009918AE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527D28289D17009918AE /* ViewController.swift */; }; + E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.debug.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.debug.xcconfig"; sourceTree = ""; }; + E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E01B527928289D17009918AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E01B527B28289D17009918AE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + E01B527D28289D17009918AE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E01B527328289D17009918AE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D660B86900EFFD9394B4E3A6 /* Pods */ = { + isa = PBXGroup; + children = ( + 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */, + EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + E01B526D28289D17009918AE = { + isa = PBXGroup; + children = ( + E01B527828289D17009918AE /* Starbucks */, + E01B527728289D17009918AE /* Products */, + D660B86900EFFD9394B4E3A6 /* Pods */, + E7EA5F1EF67A45D40CCE639E /* Frameworks */, + ); + sourceTree = ""; + }; + E01B527728289D17009918AE /* Products */ = { + isa = PBXGroup; + children = ( + E01B527628289D17009918AE /* Starbucks.app */, + ); + name = Products; + sourceTree = ""; + }; + E01B527828289D17009918AE /* Starbucks */ = { + isa = PBXGroup; + children = ( + E01B528E28289FBA009918AE /* Sources */, + E01B528F2828A0C3009918AE /* Resource */, + E01B52902828A0CE009918AE /* Configuration */, + ); + path = Starbucks; + sourceTree = ""; + }; + E01B528E28289FBA009918AE /* Sources */ = { + isa = PBXGroup; + children = ( + E01B527928289D17009918AE /* AppDelegate.swift */, + E01B527B28289D17009918AE /* SceneDelegate.swift */, + E01B527D28289D17009918AE /* ViewController.swift */, + ); + path = Sources; + sourceTree = ""; + }; + E01B528F2828A0C3009918AE /* Resource */ = { + isa = PBXGroup; + children = ( + E01B528228289D18009918AE /* Assets.xcassets */, + ); + path = Resource; + sourceTree = ""; + }; + E01B52902828A0CE009918AE /* Configuration */ = { + isa = PBXGroup; + children = ( + E01B528728289D18009918AE /* Info.plist */, + ); + path = Configuration; + sourceTree = ""; + }; + E7EA5F1EF67A45D40CCE639E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E01B527528289D17009918AE /* Starbucks */ = { + isa = PBXNativeTarget; + buildConfigurationList = E01B528A28289D18009918AE /* Build configuration list for PBXNativeTarget "Starbucks" */; + buildPhases = ( + EC48B327C00CB96D6E9C2259 /* [CP] Check Pods Manifest.lock */, + E01B527228289D17009918AE /* Sources */, + E01B527328289D17009918AE /* Frameworks */, + E01B527428289D17009918AE /* Resources */, + E01B528D28289D92009918AE /* swiftlint */, + 3CCDD8A1D594DA587B98324C /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Starbucks; + productName = Starbucks; + productReference = E01B527628289D17009918AE /* Starbucks.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E01B526E28289D17009918AE /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1330; + LastUpgradeCheck = 1330; + TargetAttributes = { + E01B527528289D17009918AE = { + CreatedOnToolsVersion = 13.3.1; + }; + }; + }; + buildConfigurationList = E01B527128289D17009918AE /* Build configuration list for PBXProject "Starbucks" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E01B526D28289D17009918AE; + productRefGroup = E01B527728289D17009918AE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E01B527528289D17009918AE /* Starbucks */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E01B527428289D17009918AE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E01B528328289D18009918AE /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3CCDD8A1D594DA587B98324C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Starbucks/Pods-Starbucks-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Starbucks/Pods-Starbucks-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Starbucks/Pods-Starbucks-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E01B528D28289D92009918AE /* swiftlint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = swiftlint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + EC48B327C00CB96D6E9C2259 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Starbucks-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E01B527228289D17009918AE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E01B527E28289D17009918AE /* ViewController.swift in Sources */, + E01B527A28289D17009918AE /* AppDelegate.swift in Sources */, + E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + E01B528828289D18009918AE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E01B528928289D18009918AE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + E01B528B28289D18009918AE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Starbucks/Configuration/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.codesquad.team21.Starbucks; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E01B528C28289D18009918AE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Starbucks/Configuration/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.codesquad.team21.Starbucks; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E01B527128289D17009918AE /* Build configuration list for PBXProject "Starbucks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E01B528828289D18009918AE /* Debug */, + E01B528928289D18009918AE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E01B528A28289D18009918AE /* Build configuration list for PBXNativeTarget "Starbucks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E01B528B28289D18009918AE /* Debug */, + E01B528C28289D18009918AE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E01B526E28289D17009918AE /* Project object */; +} diff --git a/Starbucks/Starbucks.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Starbucks/Starbucks.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Starbucks/Starbucks.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Starbucks/Starbucks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Starbucks/Starbucks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Starbucks/Starbucks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Starbucks/Starbucks.xcworkspace/contents.xcworkspacedata b/Starbucks/Starbucks.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..3a149ca --- /dev/null +++ b/Starbucks/Starbucks.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Starbucks/Starbucks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Starbucks/Starbucks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Starbucks/Starbucks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Starbucks/Starbucks/Configuration/Info.plist b/Starbucks/Starbucks/Configuration/Info.plist new file mode 100644 index 0000000..dd3c9af --- /dev/null +++ b/Starbucks/Starbucks/Configuration/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/Starbucks/Starbucks/Info.plist b/Starbucks/Starbucks/Info.plist new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/Starbucks/Starbucks/Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/AccentColor.colorset/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..5a3257a --- /dev/null +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,93 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Starbucks/Starbucks/Sources/AppDelegate.swift b/Starbucks/Starbucks/Sources/AppDelegate.swift new file mode 100644 index 0000000..cbb24c5 --- /dev/null +++ b/Starbucks/Starbucks/Sources/AppDelegate.swift @@ -0,0 +1,16 @@ +// +// AppDelegate.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } +} diff --git a/Starbucks/Starbucks/Sources/SceneDelegate.swift b/Starbucks/Starbucks/Sources/SceneDelegate.swift new file mode 100644 index 0000000..4738364 --- /dev/null +++ b/Starbucks/Starbucks/Sources/SceneDelegate.swift @@ -0,0 +1,19 @@ +// +// SceneDelegate.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let scene = (scene as? UIWindowScene) else { return } + } +} diff --git a/Starbucks/Starbucks/Sources/ViewController.swift b/Starbucks/Starbucks/Sources/ViewController.swift new file mode 100644 index 0000000..2790281 --- /dev/null +++ b/Starbucks/Starbucks/Sources/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} + From e37fad0a32a14a831d9e3a5cbdaaae4e64e36872 Mon Sep 17 00:00:00 2001 From: shingha Date: Mon, 9 May 2022 11:09:19 +0900 Subject: [PATCH 02/57] =?UTF-8?q?rxswift=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Podfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Starbucks/Podfile b/Starbucks/Podfile index ec86187..1e1c082 100644 --- a/Starbucks/Podfile +++ b/Starbucks/Podfile @@ -2,10 +2,10 @@ target 'Starbucks' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! - # Pods for sidedish - - pod 'SwiftLint' - pod 'FirebaseAuth' - pod 'GoogleSignIn' - pod 'SnapKit', '~> 5.6.0' + pod 'SwiftLint' + pod 'FirebaseAuth' + pod 'GoogleSignIn' + pod 'SnapKit', '~> 5.6.0' + pod 'RxSwift', '6.5.0' + pod 'RxCocoa', '6.5.0' end From 9c86d722ffa898c3fa99d8d647d22d8463479c73 Mon Sep 17 00:00:00 2001 From: shingha Date: Mon, 9 May 2022 11:57:32 +0900 Subject: [PATCH 03/57] readme Update --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a8d370..7630ab2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ -# swift-starbucks -iOS 클래스 프로젝트 - 스타벅스 앱 +## StackBucks App + +### ProjectSetting + +- Language: Swift +- UI: Code Base + SnapKit +- Architecture: MVVM + RxSwift + +### Developer + +* Shingha - [Git](https://github.com/shingha1124) +* Gucci - [Git](https://github.com/Damagucci-Juice) +* Mase - [Git](https://github.com/sanghyeok-kim) + +### Issue + +* [Jira](https://shingha1124.atlassian.net/jira/software/projects/STAR/boards/1) + +### Wiki + +* [Wiki](https://github.com/shingha1124/swift-starbucks/wiki) + From c9a6992fc26f11fa56530dce8114485f612ab558 Mon Sep 17 00:00:00 2001 From: shingha Date: Mon, 9 May 2022 12:16:55 +0900 Subject: [PATCH 04/57] =?UTF-8?q?=EC=8A=A4=ED=86=A0=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8D=98=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=A0=84=EB=B6=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 2 -- Starbucks/Starbucks/Configuration/Info.plist | 2 -- Starbucks/Starbucks/Sources/SceneDelegate.swift | 8 +++++--- Starbucks/Starbucks/Sources/ViewController.swift | 3 ++- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index 2745c5e..e4aa49d 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -377,7 +377,6 @@ INFOPLIST_FILE = Starbucks/Configuration/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -407,7 +406,6 @@ INFOPLIST_FILE = Starbucks/Configuration/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/Starbucks/Starbucks/Configuration/Info.plist b/Starbucks/Starbucks/Configuration/Info.plist index dd3c9af..0eb786d 100644 --- a/Starbucks/Starbucks/Configuration/Info.plist +++ b/Starbucks/Starbucks/Configuration/Info.plist @@ -15,8 +15,6 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/Starbucks/Starbucks/Sources/SceneDelegate.swift b/Starbucks/Starbucks/Sources/SceneDelegate.swift index 4738364..484c6ad 100644 --- a/Starbucks/Starbucks/Sources/SceneDelegate.swift +++ b/Starbucks/Starbucks/Sources/SceneDelegate.swift @@ -11,9 +11,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let scene = (scene as? UIWindowScene) else { return } + + self.window = UIWindow(windowScene: scene) + window?.rootViewController = ViewController() + + window?.makeKeyAndVisible() } } diff --git a/Starbucks/Starbucks/Sources/ViewController.swift b/Starbucks/Starbucks/Sources/ViewController.swift index 2790281..e05da01 100644 --- a/Starbucks/Starbucks/Sources/ViewController.swift +++ b/Starbucks/Starbucks/Sources/ViewController.swift @@ -11,7 +11,8 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. + + view.backgroundColor = .red } From d4d05656a64ca9d653960a044b3c56e66e5a3466 Mon Sep 17 00:00:00 2001 From: shingha Date: Mon, 9 May 2022 17:33:29 +0900 Subject: [PATCH 05/57] =?UTF-8?q?=ED=83=80=EA=B2=9F=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=2013.0,=20swiftlint=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index e4aa49d..f03746d 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -210,7 +210,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + shellScript = "export PATH=\"${PODS_ROOT}/SwiftLint/swiftlint\"\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; EC48B327C00CB96D6E9C2259 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -300,7 +300,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -354,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; From 6b9f29ac0e559030d3973bb72e4c7a93dfb348d7 Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Mon, 9 May 2022 18:31:53 +0900 Subject: [PATCH 06/57] =?UTF-8?q?rx=20=EA=B8=B0=EB=B3=B8=20=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Podfile | 3 +- Starbucks/Starbucks.xcodeproj/project.pbxproj | 30 +++++++-- .../Present/Order/OrderViewController.swift | 64 +++++++++++++++++++ .../Present/Order/OrderViewModel.swift | 50 +++++++++++++++ .../Starbucks/Sources/SceneDelegate.swift | 3 +- .../Starbucks/Sources/ViewController.swift | 20 ------ 6 files changed, 142 insertions(+), 28 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift delete mode 100644 Starbucks/Starbucks/Sources/ViewController.swift diff --git a/Starbucks/Podfile b/Starbucks/Podfile index 1e1c082..fe90d79 100644 --- a/Starbucks/Podfile +++ b/Starbucks/Podfile @@ -5,7 +5,8 @@ target 'Starbucks' do pod 'SwiftLint' pod 'FirebaseAuth' pod 'GoogleSignIn' - pod 'SnapKit', '~> 5.6.0' + pod 'SnapKit', '~> 5.0.1' pod 'RxSwift', '6.5.0' pod 'RxCocoa', '6.5.0' + pod 'RxAppState', '~> 1.6' end diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index f03746d..623cba4 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -7,20 +7,22 @@ objects = { /* Begin PBXBuildFile section */ + 1E89A08E28290A0300CD7625 /* OrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A08C28290A0300CD7625 /* OrderViewController.swift */; }; + 1E89A08F28290A0300CD7625 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A08D28290A0300CD7625 /* OrderViewModel.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; E01B527A28289D17009918AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527928289D17009918AE /* AppDelegate.swift */; }; E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527B28289D17009918AE /* SceneDelegate.swift */; }; - E01B527E28289D17009918AE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527D28289D17009918AE /* ViewController.swift */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 1E89A08C28290A0300CD7625 /* OrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewController.swift; sourceTree = ""; }; + 1E89A08D28290A0300CD7625 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.debug.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.debug.xcconfig"; sourceTree = ""; }; E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; E01B527928289D17009918AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E01B527B28289D17009918AE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - E01B527D28289D17009918AE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -38,6 +40,23 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1E89A08A28290A0300CD7625 /* Present */ = { + isa = PBXGroup; + children = ( + 1E89A08B28290A0300CD7625 /* Order */, + ); + path = Present; + sourceTree = ""; + }; + 1E89A08B28290A0300CD7625 /* Order */ = { + isa = PBXGroup; + children = ( + 1E89A08C28290A0300CD7625 /* OrderViewController.swift */, + 1E89A08D28290A0300CD7625 /* OrderViewModel.swift */, + ); + path = Order; + sourceTree = ""; + }; D660B86900EFFD9394B4E3A6 /* Pods */ = { isa = PBXGroup; children = ( @@ -78,9 +97,9 @@ E01B528E28289FBA009918AE /* Sources */ = { isa = PBXGroup; children = ( + 1E89A08A28290A0300CD7625 /* Present */, E01B527928289D17009918AE /* AppDelegate.swift */, E01B527B28289D17009918AE /* SceneDelegate.swift */, - E01B527D28289D17009918AE /* ViewController.swift */, ); path = Sources; sourceTree = ""; @@ -210,7 +229,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export PATH=\"${PODS_ROOT}/SwiftLint/swiftlint\"\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; }; EC48B327C00CB96D6E9C2259 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -241,9 +260,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E01B527E28289D17009918AE /* ViewController.swift in Sources */, E01B527A28289D17009918AE /* AppDelegate.swift in Sources */, E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */, + 1E89A08E28290A0300CD7625 /* OrderViewController.swift in Sources */, + 1E89A08F28290A0300CD7625 /* OrderViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift new file mode 100644 index 0000000..7efc4ab --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift @@ -0,0 +1,64 @@ +// +// OrderViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import RxAppState +import RxCocoa +import RxSwift +import SnapKit +import UIKit + +class OrderViewController: UIViewController { + + private let orderLabel: UILabel = { + let label = UILabel() + label.text = "Order" + label.font = .systemFont(ofSize: 28, weight: .bold) + label.textAlignment = .left + return label + }() + + private let viewModel: OrderViewModelProtocol + private let disposeBag = DisposeBag() + + init(viewModel: OrderViewModelProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + bind() + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + rx.viewDidLoad + .bind(to: viewModel.action().viewDidLoad) + .disposed(by: disposeBag) + + viewModel.state().test + .bind(onNext: { + print($0) + }) + .disposed(by: disposeBag) + } + + private func attribute() { + view.backgroundColor = .systemBackground + } + + private func layout() { + view.addSubview(orderLabel) + + orderLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(80) + $0.leading.trailing.equalToSuperview().inset(20) + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift new file mode 100644 index 0000000..8579bf9 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift @@ -0,0 +1,50 @@ +// +// OrderViewModel.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/09. +// + +import Foundation +import RxRelay +import RxSwift + +protocol OrderViewModelAction { + var viewDidLoad: PublishRelay { get } +} + +protocol OrderViewModelState { + var test: PublishRelay { get } +} + +protocol OrderViewModelBinding { + func action() -> OrderViewModelAction + func state() -> OrderViewModelState +} + +typealias OrderViewModelProtocol = OrderViewModelBinding + +class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelBinding { + + func action() -> OrderViewModelAction { self } + + let viewDidLoad = PublishRelay() + + func state() -> OrderViewModelState { self } + + let test = PublishRelay() + + let disposeBag = DisposeBag() + + init() { + viewDidLoad + .map { "map test" } + .bind(to: test) + .disposed(by: disposeBag) + + viewDidLoad + .map { "gucci test" } + .bind(to: test) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/SceneDelegate.swift b/Starbucks/Starbucks/Sources/SceneDelegate.swift index 484c6ad..2770aad 100644 --- a/Starbucks/Starbucks/Sources/SceneDelegate.swift +++ b/Starbucks/Starbucks/Sources/SceneDelegate.swift @@ -14,8 +14,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let scene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: scene) - window?.rootViewController = ViewController() - + window?.rootViewController = OrderViewController(viewModel: OrderViewModel()) window?.makeKeyAndVisible() } } diff --git a/Starbucks/Starbucks/Sources/ViewController.swift b/Starbucks/Starbucks/Sources/ViewController.swift deleted file mode 100644 index e05da01..0000000 --- a/Starbucks/Starbucks/Sources/ViewController.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// ViewController.swift -// Starbucks -// -// Created by seongha shin on 2022/05/09. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .red - } - - -} - From ccea198ebe91b53948b60b706078ca9c947af3e9 Mon Sep 17 00:00:00 2001 From: shingha Date: Mon, 9 May 2022 21:11:56 +0900 Subject: [PATCH 07/57] =?UTF-8?q?[feature]=20=ED=83=AD=EB=B0=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 70 +++++++++++++++++- .../AppIcon.appiconset/Contents.json | 5 ++ .../ic_temp.imageset/Contents.json | 21 ++++++ .../ic_temp.imageset/ic_temp.png | Bin 0 -> 341 bytes .../Sources/Extension/UIColor+Extension.swift | 13 ++++ .../Present/Home/HomeViewController.swift | 13 ++++ .../Sources/Present/Home/HomeViewModel.swift | 12 +++ .../Present/Pay/PayViewController.swift | 12 +++ .../Sources/Present/RootWindow.swift | 22 ++++++ .../TabBar/StarbucksViewController.swift | 32 ++++++++ .../Starbucks/Sources/SceneDelegate.swift | 4 +- 11 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/Contents.json create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/ic_temp.png create mode 100644 Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift create mode 100644 Starbucks/Starbucks/Sources/Present/RootWindow.swift create mode 100644 Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index f03746d..b9dcce0 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -12,6 +12,12 @@ E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527B28289D17009918AE /* SceneDelegate.swift */; }; E01B527E28289D17009918AE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527D28289D17009918AE /* ViewController.swift */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; + E01B5294282932CF009918AE /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5293282932CF009918AE /* HomeViewController.swift */; }; + E01B5296282932D6009918AE /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5295282932D6009918AE /* HomeViewModel.swift */; }; + E01B5298282932E3009918AE /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5297282932E3009918AE /* RootWindow.swift */; }; + E01B529B282934ED009918AE /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B529A282934ED009918AE /* StarbucksViewController.swift */; }; + E08BB6A328293746005ADEFA /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A228293746005ADEFA /* PayViewController.swift */; }; + E08BB6A62829398F005ADEFA /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -23,6 +29,12 @@ E01B527D28289D17009918AE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E01B5293282932CF009918AE /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + E01B5295282932D6009918AE /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + E01B5297282932E3009918AE /* RootWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; + E01B529A282934ED009918AE /* StarbucksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; + E08BB6A228293746005ADEFA /* PayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; + E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -78,6 +90,8 @@ E01B528E28289FBA009918AE /* Sources */ = { isa = PBXGroup; children = ( + E01B529128293256009918AE /* Present */, + E08BB6A42829397E005ADEFA /* Extension */, E01B527928289D17009918AE /* AppDelegate.swift */, E01B527B28289D17009918AE /* SceneDelegate.swift */, E01B527D28289D17009918AE /* ViewController.swift */, @@ -101,6 +115,50 @@ path = Configuration; sourceTree = ""; }; + E01B529128293256009918AE /* Present */ = { + isa = PBXGroup; + children = ( + E01B5299282934D2009918AE /* TabBar */, + E01B5292282932BC009918AE /* Home */, + E08BB6A12829373D005ADEFA /* Pay */, + E01B5297282932E3009918AE /* RootWindow.swift */, + ); + path = Present; + sourceTree = ""; + }; + E01B5292282932BC009918AE /* Home */ = { + isa = PBXGroup; + children = ( + E01B5293282932CF009918AE /* HomeViewController.swift */, + E01B5295282932D6009918AE /* HomeViewModel.swift */, + ); + path = Home; + sourceTree = ""; + }; + E01B5299282934D2009918AE /* TabBar */ = { + isa = PBXGroup; + children = ( + E01B529A282934ED009918AE /* StarbucksViewController.swift */, + ); + path = TabBar; + sourceTree = ""; + }; + E08BB6A12829373D005ADEFA /* Pay */ = { + isa = PBXGroup; + children = ( + E08BB6A228293746005ADEFA /* PayViewController.swift */, + ); + path = Pay; + sourceTree = ""; + }; + E08BB6A42829397E005ADEFA /* Extension */ = { + isa = PBXGroup; + children = ( + E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */, + ); + path = Extension; + sourceTree = ""; + }; E7EA5F1EF67A45D40CCE639E /* Frameworks */ = { isa = PBXGroup; children = ( @@ -210,7 +268,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export PATH=\"${PODS_ROOT}/SwiftLint/swiftlint\"\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; }; EC48B327C00CB96D6E9C2259 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -241,9 +299,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E01B5296282932D6009918AE /* HomeViewModel.swift in Sources */, E01B527E28289D17009918AE /* ViewController.swift in Sources */, E01B527A28289D17009918AE /* AppDelegate.swift in Sources */, + E08BB6A62829398F005ADEFA /* UIColor+Extension.swift in Sources */, + E08BB6A328293746005ADEFA /* PayViewController.swift in Sources */, + E01B529B282934ED009918AE /* StarbucksViewController.swift in Sources */, + E01B5298282932E3009918AE /* RootWindow.swift in Sources */, E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */, + E01B5294282932CF009918AE /* HomeViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -300,7 +364,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -354,7 +418,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json index 5a3257a..9221b9b 100644 --- a/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -70,6 +70,11 @@ "scale" : "2x", "size" : "40x40" }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, { "idiom" : "ipad", "scale" : "2x", diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/Contents.json new file mode 100644 index 0000000..48b0ec9 --- /dev/null +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ic_temp.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/ic_temp.png b/Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/ic_temp.png new file mode 100644 index 0000000000000000000000000000000000000000..ce11ce516bc3e8fad1691b02cf46b78fa0ac718a GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf2?- zRuE=<^ydFTAVadmHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXprT8jE{-7{ zoqH!aavd@daFu_=x~HMK|DmRf*IiD%FG^Jm>y__0m4uf>2W)U%xXV)^BTN15k;a=o zKi>0tI>c!fn9XH-;$q6K^&oW@Q`p7_t0r0P=vu;g&r_*&`=pjb%__MMl{~|L_;-rk z(VnS!IM$P^pp#+y+^>JmFN_Xe(6gbI(PvExLypM9?88Um0{K#+!jmE_9pWWcG_2ix zL?FU~``WaS4b0QzHwzl%ae38WJZrq$tLftYm+`EvO5fiJziC{~^d~VQzKMAPgS!6< e2?Osto6RGdA3e@IDYOabI|ffzKbLh*2~7YXM1acx literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift new file mode 100644 index 0000000..ff4ecfe --- /dev/null +++ b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift @@ -0,0 +1,13 @@ +// +// UIColor+Extension.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import UIKit + +extension UIColor { + static let green1 = UIColor(red: 0, green: 176 / 255, blue: 111 / 255, alpha: 1) + static let grey145 = UIColor(white: 145 / 255, alpha: 1) +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift new file mode 100644 index 0000000..2271c4f --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -0,0 +1,13 @@ +// +// MainViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import UIKit + +class HomeViewController: UIViewController { + + +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift new file mode 100644 index 0000000..da62b5d --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -0,0 +1,12 @@ +// +// MainViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import Foundation + +class HomeViewModel { + +} diff --git a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift new file mode 100644 index 0000000..6a74181 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift @@ -0,0 +1,12 @@ +// +// PayViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import UIKit + +class PayViewController: UIViewController { + +} diff --git a/Starbucks/Starbucks/Sources/Present/RootWindow.swift b/Starbucks/Starbucks/Sources/Present/RootWindow.swift new file mode 100644 index 0000000..0853bdc --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/RootWindow.swift @@ -0,0 +1,22 @@ +// +// RootWindow.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import RxSwift +import UIKit + +class RootWindow: UIWindow { + override init(windowScene: UIWindowScene) { + super.init(windowScene: windowScene) + overrideUserInterfaceStyle = .light + rootViewController = StarbucksViewController() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift new file mode 100644 index 0000000..5cd1050 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -0,0 +1,32 @@ +// +// StarbucksTabBarViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import UIKit + +class StarbucksViewController: UITabBarController { + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + tabBar.tintColor = .green1 + tabBar.unselectedItemTintColor = .grey145 + setUpTabBar() + } + + private func setUpTabBar() { + let homeViewController = HomeViewController() + homeViewController.tabBarItem.title = "Home" + homeViewController.tabBarItem.image = UIImage(named: "ic_temp") + + let payViewController = PayViewController() + payViewController.tabBarItem.title = "Pay" + payViewController.tabBarItem.image = UIImage(named: "ic_temp") + + viewControllers = [ + homeViewController, payViewController + ] + } +} diff --git a/Starbucks/Starbucks/Sources/SceneDelegate.swift b/Starbucks/Starbucks/Sources/SceneDelegate.swift index 484c6ad..c8af78b 100644 --- a/Starbucks/Starbucks/Sources/SceneDelegate.swift +++ b/Starbucks/Starbucks/Sources/SceneDelegate.swift @@ -13,9 +13,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let scene = (scene as? UIWindowScene) else { return } - self.window = UIWindow(windowScene: scene) - window?.rootViewController = ViewController() - + self.window = RootWindow(windowScene: scene) window?.makeKeyAndVisible() } } From 28d86585004115979e9de5728f214ef26d58d30c Mon Sep 17 00:00:00 2001 From: shingha Date: Tue, 10 May 2022 09:58:22 +0900 Subject: [PATCH 08/57] =?UTF-8?q?[feature]=20home,=20pay=20viewcontroller?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Podfile | 15 +- Starbucks/Starbucks.xcodeproj/project.pbxproj | 180 ++++++++++++++++++ .../Present/Home/HomeViewController.swift | 23 +++ .../Sources/Present/Home/HomeViewModel.swift | 24 ++- .../TabBar/StarbucksViewController.swift | 2 +- Starbucks/StarbucksTests/StarbucksTests.swift | 21 ++ 6 files changed, 260 insertions(+), 5 deletions(-) create mode 100644 Starbucks/StarbucksTests/StarbucksTests.swift diff --git a/Starbucks/Podfile b/Starbucks/Podfile index 1e1c082..eafe448 100644 --- a/Starbucks/Podfile +++ b/Starbucks/Podfile @@ -1,11 +1,20 @@ -target 'Starbucks' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! +use_frameworks! +target 'Starbucks' do pod 'SwiftLint' + pod 'FirebaseAuth' pod 'GoogleSignIn' + pod 'SnapKit', '~> 5.6.0' + pod 'RxSwift', '6.5.0' pod 'RxCocoa', '6.5.0' + pod 'RxAppState', '~> 1.6' +end + +target 'StarbucksTests' do + #Quick은 테스트를 동작 주도 개발로 작성할 수 있도록 도와주는 프레임워크 + pod 'Quick' + pod 'Nimble' end diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index b9dcce0..c39f45e 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; + 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E01B527A28289D17009918AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527928289D17009918AE /* AppDelegate.swift */; }; E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527B28289D17009918AE /* SceneDelegate.swift */; }; E01B527E28289D17009918AE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527D28289D17009918AE /* ViewController.swift */; }; @@ -18,9 +19,23 @@ E01B529B282934ED009918AE /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B529A282934ED009918AE /* StarbucksViewController.swift */; }; E08BB6A328293746005ADEFA /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A228293746005ADEFA /* PayViewController.swift */; }; E08BB6A62829398F005ADEFA /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */; }; + E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + E08BB6B028294347005ADEFA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E01B526E28289D17009918AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = E01B527528289D17009918AE; + remoteInfo = Starbucks; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.debug.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.debug.xcconfig"; sourceTree = ""; }; + 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; + 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.debug.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.debug.xcconfig"; sourceTree = ""; }; E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -35,6 +50,8 @@ E01B529A282934ED009918AE /* StarbucksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; E08BB6A228293746005ADEFA /* PayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -47,6 +64,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E08BB6A928294347005ADEFA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -55,6 +80,8 @@ children = ( 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */, EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */, + 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */, + 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -63,6 +90,7 @@ isa = PBXGroup; children = ( E01B527828289D17009918AE /* Starbucks */, + E08BB6AD28294347005ADEFA /* StarbucksTests */, E01B527728289D17009918AE /* Products */, D660B86900EFFD9394B4E3A6 /* Pods */, E7EA5F1EF67A45D40CCE639E /* Frameworks */, @@ -73,6 +101,7 @@ isa = PBXGroup; children = ( E01B527628289D17009918AE /* Starbucks.app */, + E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,6 +119,7 @@ E01B528E28289FBA009918AE /* Sources */ = { isa = PBXGroup; children = ( + E08BB6A728293F83005ADEFA /* Repository */, E01B529128293256009918AE /* Present */, E08BB6A42829397E005ADEFA /* Extension */, E01B527928289D17009918AE /* AppDelegate.swift */, @@ -159,10 +189,26 @@ path = Extension; sourceTree = ""; }; + E08BB6A728293F83005ADEFA /* Repository */ = { + isa = PBXGroup; + children = ( + ); + path = Repository; + sourceTree = ""; + }; + E08BB6AD28294347005ADEFA /* StarbucksTests */ = { + isa = PBXGroup; + children = ( + E08BB6AE28294347005ADEFA /* StarbucksTests.swift */, + ); + path = StarbucksTests; + sourceTree = ""; + }; E7EA5F1EF67A45D40CCE639E /* Frameworks */ = { isa = PBXGroup; children = ( 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */, + 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -190,6 +236,26 @@ productReference = E01B527628289D17009918AE /* Starbucks.app */; productType = "com.apple.product-type.application"; }; + E08BB6AB28294347005ADEFA /* StarbucksTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E08BB6B228294347005ADEFA /* Build configuration list for PBXNativeTarget "StarbucksTests" */; + buildPhases = ( + 0A79A6CCEA1B0130AC77F125 /* [CP] Check Pods Manifest.lock */, + E08BB6A828294347005ADEFA /* Sources */, + E08BB6A928294347005ADEFA /* Frameworks */, + E08BB6AA28294347005ADEFA /* Resources */, + 2E874F306299B9BA4B865F96 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + E08BB6B128294347005ADEFA /* PBXTargetDependency */, + ); + name = StarbucksTests; + productName = StarbucksTests; + productReference = E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -203,6 +269,10 @@ E01B527528289D17009918AE = { CreatedOnToolsVersion = 13.3.1; }; + E08BB6AB28294347005ADEFA = { + CreatedOnToolsVersion = 13.3.1; + TestTargetID = E01B527528289D17009918AE; + }; }; }; buildConfigurationList = E01B527128289D17009918AE /* Build configuration list for PBXProject "Starbucks" */; @@ -219,6 +289,7 @@ projectRoot = ""; targets = ( E01B527528289D17009918AE /* Starbucks */, + E08BB6AB28294347005ADEFA /* StarbucksTests */, ); }; /* End PBXProject section */ @@ -232,9 +303,55 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E08BB6AA28294347005ADEFA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0A79A6CCEA1B0130AC77F125 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-StarbucksTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2E874F306299B9BA4B865F96 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3CCDD8A1D594DA587B98324C /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -311,8 +428,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E08BB6A828294347005ADEFA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + E08BB6B128294347005ADEFA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E01B527528289D17009918AE /* Starbucks */; + targetProxy = E08BB6B028294347005ADEFA /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ E01B528828289D18009918AE /* Debug */ = { isa = XCBuildConfiguration; @@ -486,6 +619,44 @@ }; name = Release; }; + E08BB6B328294347005ADEFA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.codesquad.team21.StarbucksTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Starbucks.app/Starbucks"; + }; + name = Debug; + }; + E08BB6B428294347005ADEFA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.codesquad.team21.StarbucksTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Starbucks.app/Starbucks"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -507,6 +678,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + E08BB6B228294347005ADEFA /* Build configuration list for PBXNativeTarget "StarbucksTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E08BB6B328294347005ADEFA /* Debug */, + E08BB6B428294347005ADEFA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = E01B526E28289D17009918AE /* Project object */; diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 2271c4f..74de6a2 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -5,9 +5,32 @@ // Created by seongha shin on 2022/05/09. // +import RxAppState +import RxSwift import UIKit class HomeViewController: UIViewController { + private let viewModel: HomeViewModelProtocol + private let disposeBag = DisposeBag() + init(viewModel: HomeViewModelProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + bind() + layout() + } + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + rx.viewDidLoad + .bind(to: viewModel.action().loadHomeData) + .disposed(by: disposeBag) + } + + private func layout() { + } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index da62b5d..11af130 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -6,7 +6,29 @@ // import Foundation +import RxRelay -class HomeViewModel { +protocol HomeViewModelAction { + var loadHomeData: PublishRelay { get } +} + +protocol HomeViewModelState { +} + +protocol HomeViewModelBinding { + func action() -> HomeViewModelAction + func state() -> HomeViewModelState +} + +typealias HomeViewModelProtocol = HomeViewModelBinding + +class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelState { + func action() -> HomeViewModelAction { self } + + let loadHomeData = PublishRelay() + + func state() -> HomeViewModelState { self } + init() { + } } diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index 5cd1050..20dbb40 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -17,7 +17,7 @@ class StarbucksViewController: UITabBarController { } private func setUpTabBar() { - let homeViewController = HomeViewController() + let homeViewController = HomeViewController(viewModel: HomeViewModel()) homeViewController.tabBarItem.title = "Home" homeViewController.tabBarItem.image = UIImage(named: "ic_temp") diff --git a/Starbucks/StarbucksTests/StarbucksTests.swift b/Starbucks/StarbucksTests/StarbucksTests.swift new file mode 100644 index 0000000..49d2c7f --- /dev/null +++ b/Starbucks/StarbucksTests/StarbucksTests.swift @@ -0,0 +1,21 @@ +// +// StarbucksTests.swift +// StarbucksTests +// +// Created by seongha shin on 2022/05/09. +// + +import Nimble +import Quick +import XCTest + +@testable import Starbucks + +class StarbucksTests: QuickSpec { + + override func setUp() { + describe("사용자 테스트") { + + } + } +} From 285a5d03f1df4de2971d3f70fdc41d1e84a6d36f Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Tue, 10 May 2022 10:53:39 +0900 Subject: [PATCH 09/57] =?UTF-8?q?button=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/Order/OrderViewController.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift index 7efc4ab..88521fd 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift @@ -21,6 +21,13 @@ class OrderViewController: UIViewController { return label }() + private let button: UIButton = { + let button = UIButton() + button.backgroundColor = .red + button.setTitle("asd", for: .normal) + return button + }() + private let viewModel: OrderViewModelProtocol private let disposeBag = DisposeBag() @@ -47,6 +54,10 @@ class OrderViewController: UIViewController { print($0) }) .disposed(by: disposeBag) + + button.rx.tap + .bind(to: viewModel.action().viewDidLoad) + .disposed(by: disposeBag) } private func attribute() { @@ -55,10 +66,17 @@ class OrderViewController: UIViewController { private func layout() { view.addSubview(orderLabel) + view.addSubview(button) orderLabel.snp.makeConstraints { $0.top.equalToSuperview().offset(80) $0.leading.trailing.equalToSuperview().inset(20) } + + button.snp.makeConstraints { + $0.width.height.equalTo(100) + $0.top.equalTo(orderLabel.snp.bottom).offset(20) + $0.leading.equalToSuperview().offset(20) + } } } From 5b060449f5bddc308fcfb4c5888ca922161463c0 Mon Sep 17 00:00:00 2001 From: shingha Date: Tue, 10 May 2022 10:56:15 +0900 Subject: [PATCH 10/57] =?UTF-8?q?[feature]=20network=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Podfile | 4 +- Starbucks/Starbucks.xcodeproj/project.pbxproj | 36 +++++++++++++++ .../Starbucks/Sources/API/Base/APIError.swift | 12 +++++ .../Sources/API/Base/BaseTarget.swift | 22 ++++++++++ .../Sources/API/Base/HTTPContentType.swift | 22 ++++++++++ .../Sources/API/Base/HTTPMethod.swift | 16 +++++++ .../Starbucks/Sources/API/Base/Provider.swift | 44 +++++++++++++++++++ 7 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/API/Base/APIError.swift create mode 100644 Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift create mode 100644 Starbucks/Starbucks/Sources/API/Base/HTTPContentType.swift create mode 100644 Starbucks/Starbucks/Sources/API/Base/HTTPMethod.swift create mode 100644 Starbucks/Starbucks/Sources/API/Base/Provider.swift diff --git a/Starbucks/Podfile b/Starbucks/Podfile index eafe448..d7f2087 100644 --- a/Starbucks/Podfile +++ b/Starbucks/Podfile @@ -3,10 +3,8 @@ use_frameworks! target 'Starbucks' do pod 'SwiftLint' - pod 'FirebaseAuth' - pod 'GoogleSignIn' - pod 'SnapKit', '~> 5.6.0' + pod 'Alamofire' pod 'RxSwift', '6.5.0' pod 'RxCocoa', '6.5.0' diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index c39f45e..fda0e2a 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -17,6 +17,11 @@ E01B5296282932D6009918AE /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5295282932D6009918AE /* HomeViewModel.swift */; }; E01B5298282932E3009918AE /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5297282932E3009918AE /* RootWindow.swift */; }; E01B529B282934ED009918AE /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B529A282934ED009918AE /* StarbucksViewController.swift */; }; + E07230252829F23500AF3E16 /* BaseTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230242829F23500AF3E16 /* BaseTarget.swift */; }; + E07230282829F2E200AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230272829F2E200AF3E16 /* HTTPMethod.swift */; }; + E072302A2829F33100AF3E16 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230292829F33100AF3E16 /* Provider.swift */; }; + E072302C2829F4C400AF3E16 /* HTTPContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072302B2829F4C400AF3E16 /* HTTPContentType.swift */; }; + E072302E2829F6D600AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072302D2829F6D600AF3E16 /* APIError.swift */; }; E08BB6A328293746005ADEFA /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A228293746005ADEFA /* PayViewController.swift */; }; E08BB6A62829398F005ADEFA /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; @@ -48,6 +53,11 @@ E01B5295282932D6009918AE /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; E01B5297282932E3009918AE /* RootWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; E01B529A282934ED009918AE /* StarbucksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; + E07230242829F23500AF3E16 /* BaseTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTarget.swift; sourceTree = ""; }; + E07230272829F2E200AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + E07230292829F33100AF3E16 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; + E072302B2829F4C400AF3E16 /* HTTPContentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPContentType.swift; sourceTree = ""; }; + E072302D2829F6D600AF3E16 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; E08BB6A228293746005ADEFA /* PayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -119,6 +129,7 @@ E01B528E28289FBA009918AE /* Sources */ = { isa = PBXGroup; children = ( + E07230232829F22C00AF3E16 /* API */, E08BB6A728293F83005ADEFA /* Repository */, E01B529128293256009918AE /* Present */, E08BB6A42829397E005ADEFA /* Extension */, @@ -173,6 +184,26 @@ path = TabBar; sourceTree = ""; }; + E07230232829F22C00AF3E16 /* API */ = { + isa = PBXGroup; + children = ( + E07230262829F2D400AF3E16 /* Base */, + ); + path = API; + sourceTree = ""; + }; + E07230262829F2D400AF3E16 /* Base */ = { + isa = PBXGroup; + children = ( + E07230242829F23500AF3E16 /* BaseTarget.swift */, + E07230292829F33100AF3E16 /* Provider.swift */, + E07230272829F2E200AF3E16 /* HTTPMethod.swift */, + E072302B2829F4C400AF3E16 /* HTTPContentType.swift */, + E072302D2829F6D600AF3E16 /* APIError.swift */, + ); + path = Base; + sourceTree = ""; + }; E08BB6A12829373D005ADEFA /* Pay */ = { isa = PBXGroup; children = ( @@ -417,14 +448,19 @@ buildActionMask = 2147483647; files = ( E01B5296282932D6009918AE /* HomeViewModel.swift in Sources */, + E07230252829F23500AF3E16 /* BaseTarget.swift in Sources */, E01B527E28289D17009918AE /* ViewController.swift in Sources */, + E07230282829F2E200AF3E16 /* HTTPMethod.swift in Sources */, + E072302A2829F33100AF3E16 /* Provider.swift in Sources */, E01B527A28289D17009918AE /* AppDelegate.swift in Sources */, E08BB6A62829398F005ADEFA /* UIColor+Extension.swift in Sources */, E08BB6A328293746005ADEFA /* PayViewController.swift in Sources */, + E072302C2829F4C400AF3E16 /* HTTPContentType.swift in Sources */, E01B529B282934ED009918AE /* StarbucksViewController.swift in Sources */, E01B5298282932E3009918AE /* RootWindow.swift in Sources */, E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */, E01B5294282932CF009918AE /* HomeViewController.swift in Sources */, + E072302E2829F6D600AF3E16 /* APIError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Starbucks/Starbucks/Sources/API/Base/APIError.swift b/Starbucks/Starbucks/Sources/API/Base/APIError.swift new file mode 100644 index 0000000..3726b02 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/APIError.swift @@ -0,0 +1,12 @@ +// +// APIError.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +enum APIError: Error { +case custom(message: String, debugMessage: String) +} diff --git a/Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift b/Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift new file mode 100644 index 0000000..9df8c0c --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift @@ -0,0 +1,22 @@ +// +// BaseTarget.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +protocol BaseTarget { + var baseURL: URL? { get } + var path: String { get } + var parameter: [String: Any]? { get } + var method: HTTPMethod { get } + var content: HTTPContentType { get } +} + +extension BaseTarget { + var header: [String: String]? { + ["Content-Type": content.value] + } +} diff --git a/Starbucks/Starbucks/Sources/API/Base/HTTPContentType.swift b/Starbucks/Starbucks/Sources/API/Base/HTTPContentType.swift new file mode 100644 index 0000000..74a8fb4 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/HTTPContentType.swift @@ -0,0 +1,22 @@ +// +// HTTPContentType.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +enum HTTPContentType: String { + case json + case urlencode + + var value: String { + switch self { + case .json: + return "application/json; charset=utf-8" + case .urlencode: + return "application/x-www-form-urlencoded; charset=utf-8" + } + } +} diff --git a/Starbucks/Starbucks/Sources/API/Base/HTTPMethod.swift b/Starbucks/Starbucks/Sources/API/Base/HTTPMethod.swift new file mode 100644 index 0000000..e32279e --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/HTTPMethod.swift @@ -0,0 +1,16 @@ +// +// HTTPMethod.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +enum HTTPMethod: String { + case get, post + + var value: String { + self.rawValue.uppercased() + } +} diff --git a/Starbucks/Starbucks/Sources/API/Base/Provider.swift b/Starbucks/Starbucks/Sources/API/Base/Provider.swift new file mode 100644 index 0000000..bee5ea2 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/Provider.swift @@ -0,0 +1,44 @@ +// +// Provider.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Alamofire +import Foundation +import RxSwift + +class Provider { + private static func createRequest(_ target: Target) -> URLRequest? { + guard let url = target.baseURL?.appendingPathComponent(target.path) else { + return nil + } + + var request = URLRequest(url: url) + request.httpMethod = target.method.value + target.header?.forEach { key, value in + request.addValue(value, forHTTPHeaderField: key) + } + + if target.content == .urlencode { + if let param = target.parameter { + let formDataString = param.reduce(into: "") { + $0 = $0 + "\($1.key)=\($1.value)&" + }.dropLast() + + request.httpBody = formDataString.data(using: .utf8, allowLossyConversion: true) + } + } else { + if let param = target.parameter, + let body = try? JSONSerialization.data(withJSONObject: param, options: .init()) { + request.httpBody = body + } + } + return request + } + + func request(_ target: Target) { +// Single.crea + } +} From e474337b5b20a20fae11538f0a2fb080b08859b5 Mon Sep 17 00:00:00 2001 From: shingha Date: Tue, 10 May 2022 10:58:44 +0900 Subject: [PATCH 11/57] =?UTF-8?q?[feature]=20order=20View=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 180 +++++++++--------- .../TabBar/StarbucksViewController.swift | 6 +- 2 files changed, 98 insertions(+), 88 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index fda0e2a..dc70c77 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -9,21 +9,22 @@ /* Begin PBXBuildFile section */ 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; - E01B527A28289D17009918AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527928289D17009918AE /* AppDelegate.swift */; }; - E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527B28289D17009918AE /* SceneDelegate.swift */; }; - E01B527E28289D17009918AE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B527D28289D17009918AE /* ViewController.swift */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; - E01B5294282932CF009918AE /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5293282932CF009918AE /* HomeViewController.swift */; }; - E01B5296282932D6009918AE /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5295282932D6009918AE /* HomeViewModel.swift */; }; - E01B5298282932E3009918AE /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B5297282932E3009918AE /* RootWindow.swift */; }; - E01B529B282934ED009918AE /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E01B529A282934ED009918AE /* StarbucksViewController.swift */; }; - E07230252829F23500AF3E16 /* BaseTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230242829F23500AF3E16 /* BaseTarget.swift */; }; - E07230282829F2E200AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230272829F2E200AF3E16 /* HTTPMethod.swift */; }; - E072302A2829F33100AF3E16 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230292829F33100AF3E16 /* Provider.swift */; }; - E072302C2829F4C400AF3E16 /* HTTPContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072302B2829F4C400AF3E16 /* HTTPContentType.swift */; }; - E072302E2829F6D600AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072302D2829F6D600AF3E16 /* APIError.swift */; }; - E08BB6A328293746005ADEFA /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A228293746005ADEFA /* PayViewController.swift */; }; - E08BB6A62829398F005ADEFA /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */; }; + E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230322829FDF900AF3E16 /* OrderViewController.swift */; }; + E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230332829FDF900AF3E16 /* OrderViewModel.swift */; }; + E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230352829FDF900AF3E16 /* HomeViewModel.swift */; }; + E072304A2829FDF900AF3E16 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230362829FDF900AF3E16 /* HomeViewController.swift */; }; + E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230372829FDF900AF3E16 /* RootWindow.swift */; }; + E072304C2829FDF900AF3E16 /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230392829FDF900AF3E16 /* PayViewController.swift */; }; + E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072303B2829FDF900AF3E16 /* StarbucksViewController.swift */; }; + E072304E2829FDF900AF3E16 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072303D2829FDF900AF3E16 /* UIColor+Extension.swift */; }; + E072304F2829FDF900AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230402829FDF900AF3E16 /* APIError.swift */; }; + E07230502829FDF900AF3E16 /* HTTPContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230412829FDF900AF3E16 /* HTTPContentType.swift */; }; + E07230512829FDF900AF3E16 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230422829FDF900AF3E16 /* Provider.swift */; }; + E07230522829FDF900AF3E16 /* BaseTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230432829FDF900AF3E16 /* BaseTarget.swift */; }; + E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230442829FDF900AF3E16 /* HTTPMethod.swift */; }; + E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230452829FDF900AF3E16 /* AppDelegate.swift */; }; + E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230462829FDF900AF3E16 /* SceneDelegate.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -44,22 +45,23 @@ 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.debug.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.debug.xcconfig"; sourceTree = ""; }; E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; - E01B527928289D17009918AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - E01B527B28289D17009918AE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - E01B527D28289D17009918AE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E01B5293282932CF009918AE /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; - E01B5295282932D6009918AE /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; - E01B5297282932E3009918AE /* RootWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; - E01B529A282934ED009918AE /* StarbucksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; - E07230242829F23500AF3E16 /* BaseTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTarget.swift; sourceTree = ""; }; - E07230272829F2E200AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; - E07230292829F33100AF3E16 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; - E072302B2829F4C400AF3E16 /* HTTPContentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPContentType.swift; sourceTree = ""; }; - E072302D2829F6D600AF3E16 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; - E08BB6A228293746005ADEFA /* PayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; - E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + E07230322829FDF900AF3E16 /* OrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewController.swift; sourceTree = ""; }; + E07230332829FDF900AF3E16 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; + E07230352829FDF900AF3E16 /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + E07230362829FDF900AF3E16 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + E07230372829FDF900AF3E16 /* RootWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; + E07230392829FDF900AF3E16 /* PayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; + E072303B2829FDF900AF3E16 /* StarbucksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; + E072303D2829FDF900AF3E16 /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + E07230402829FDF900AF3E16 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; + E07230412829FDF900AF3E16 /* HTTPContentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPContentType.swift; sourceTree = ""; }; + E07230422829FDF900AF3E16 /* Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; + E07230432829FDF900AF3E16 /* BaseTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTarget.swift; sourceTree = ""; }; + E07230442829FDF900AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + E07230452829FDF900AF3E16 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E07230462829FDF900AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -119,27 +121,13 @@ E01B527828289D17009918AE /* Starbucks */ = { isa = PBXGroup; children = ( - E01B528E28289FBA009918AE /* Sources */, + E072302F2829FDF900AF3E16 /* Sources */, E01B528F2828A0C3009918AE /* Resource */, E01B52902828A0CE009918AE /* Configuration */, ); path = Starbucks; sourceTree = ""; }; - E01B528E28289FBA009918AE /* Sources */ = { - isa = PBXGroup; - children = ( - E07230232829F22C00AF3E16 /* API */, - E08BB6A728293F83005ADEFA /* Repository */, - E01B529128293256009918AE /* Present */, - E08BB6A42829397E005ADEFA /* Extension */, - E01B527928289D17009918AE /* AppDelegate.swift */, - E01B527B28289D17009918AE /* SceneDelegate.swift */, - E01B527D28289D17009918AE /* ViewController.swift */, - ); - path = Sources; - sourceTree = ""; - }; E01B528F2828A0C3009918AE /* Resource */ = { isa = PBXGroup; children = ( @@ -156,75 +144,90 @@ path = Configuration; sourceTree = ""; }; - E01B529128293256009918AE /* Present */ = { + E072302F2829FDF900AF3E16 /* Sources */ = { isa = PBXGroup; children = ( - E01B5299282934D2009918AE /* TabBar */, - E01B5292282932BC009918AE /* Home */, - E08BB6A12829373D005ADEFA /* Pay */, - E01B5297282932E3009918AE /* RootWindow.swift */, + E07230302829FDF900AF3E16 /* Present */, + E072303C2829FDF900AF3E16 /* Extension */, + E072303E2829FDF900AF3E16 /* API */, + E07230452829FDF900AF3E16 /* AppDelegate.swift */, + E07230462829FDF900AF3E16 /* SceneDelegate.swift */, ); - path = Present; + path = Sources; sourceTree = ""; }; - E01B5292282932BC009918AE /* Home */ = { + E07230302829FDF900AF3E16 /* Present */ = { isa = PBXGroup; children = ( - E01B5293282932CF009918AE /* HomeViewController.swift */, - E01B5295282932D6009918AE /* HomeViewModel.swift */, + E07230312829FDF900AF3E16 /* Order */, + E07230342829FDF900AF3E16 /* Home */, + E07230372829FDF900AF3E16 /* RootWindow.swift */, + E07230382829FDF900AF3E16 /* Pay */, + E072303A2829FDF900AF3E16 /* TabBar */, ); - path = Home; + path = Present; sourceTree = ""; }; - E01B5299282934D2009918AE /* TabBar */ = { + E07230312829FDF900AF3E16 /* Order */ = { isa = PBXGroup; children = ( - E01B529A282934ED009918AE /* StarbucksViewController.swift */, + E07230322829FDF900AF3E16 /* OrderViewController.swift */, + E07230332829FDF900AF3E16 /* OrderViewModel.swift */, ); - path = TabBar; + path = Order; sourceTree = ""; }; - E07230232829F22C00AF3E16 /* API */ = { + E07230342829FDF900AF3E16 /* Home */ = { isa = PBXGroup; children = ( - E07230262829F2D400AF3E16 /* Base */, + E07230352829FDF900AF3E16 /* HomeViewModel.swift */, + E07230362829FDF900AF3E16 /* HomeViewController.swift */, ); - path = API; + path = Home; sourceTree = ""; }; - E07230262829F2D400AF3E16 /* Base */ = { + E07230382829FDF900AF3E16 /* Pay */ = { isa = PBXGroup; children = ( - E07230242829F23500AF3E16 /* BaseTarget.swift */, - E07230292829F33100AF3E16 /* Provider.swift */, - E07230272829F2E200AF3E16 /* HTTPMethod.swift */, - E072302B2829F4C400AF3E16 /* HTTPContentType.swift */, - E072302D2829F6D600AF3E16 /* APIError.swift */, + E07230392829FDF900AF3E16 /* PayViewController.swift */, ); - path = Base; + path = Pay; sourceTree = ""; }; - E08BB6A12829373D005ADEFA /* Pay */ = { + E072303A2829FDF900AF3E16 /* TabBar */ = { isa = PBXGroup; children = ( - E08BB6A228293746005ADEFA /* PayViewController.swift */, + E072303B2829FDF900AF3E16 /* StarbucksViewController.swift */, ); - path = Pay; + path = TabBar; sourceTree = ""; }; - E08BB6A42829397E005ADEFA /* Extension */ = { + E072303C2829FDF900AF3E16 /* Extension */ = { isa = PBXGroup; children = ( - E08BB6A52829398F005ADEFA /* UIColor+Extension.swift */, + E072303D2829FDF900AF3E16 /* UIColor+Extension.swift */, ); path = Extension; sourceTree = ""; }; - E08BB6A728293F83005ADEFA /* Repository */ = { + E072303E2829FDF900AF3E16 /* API */ = { + isa = PBXGroup; + children = ( + E072303F2829FDF900AF3E16 /* Base */, + ); + path = API; + sourceTree = ""; + }; + E072303F2829FDF900AF3E16 /* Base */ = { isa = PBXGroup; children = ( + E07230402829FDF900AF3E16 /* APIError.swift */, + E07230412829FDF900AF3E16 /* HTTPContentType.swift */, + E07230422829FDF900AF3E16 /* Provider.swift */, + E07230432829FDF900AF3E16 /* BaseTarget.swift */, + E07230442829FDF900AF3E16 /* HTTPMethod.swift */, ); - path = Repository; + path = Base; sourceTree = ""; }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { @@ -447,20 +450,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E01B5296282932D6009918AE /* HomeViewModel.swift in Sources */, - E07230252829F23500AF3E16 /* BaseTarget.swift in Sources */, - E01B527E28289D17009918AE /* ViewController.swift in Sources */, - E07230282829F2E200AF3E16 /* HTTPMethod.swift in Sources */, - E072302A2829F33100AF3E16 /* Provider.swift in Sources */, - E01B527A28289D17009918AE /* AppDelegate.swift in Sources */, - E08BB6A62829398F005ADEFA /* UIColor+Extension.swift in Sources */, - E08BB6A328293746005ADEFA /* PayViewController.swift in Sources */, - E072302C2829F4C400AF3E16 /* HTTPContentType.swift in Sources */, - E01B529B282934ED009918AE /* StarbucksViewController.swift in Sources */, - E01B5298282932E3009918AE /* RootWindow.swift in Sources */, - E01B527C28289D17009918AE /* SceneDelegate.swift in Sources */, - E01B5294282932CF009918AE /* HomeViewController.swift in Sources */, - E072302E2829F6D600AF3E16 /* APIError.swift in Sources */, + E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */, + E072304C2829FDF900AF3E16 /* PayViewController.swift in Sources */, + E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */, + E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */, + E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */, + E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */, + E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */, + E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */, + E072304A2829FDF900AF3E16 /* HomeViewController.swift in Sources */, + E07230512829FDF900AF3E16 /* Provider.swift in Sources */, + E072304E2829FDF900AF3E16 /* UIColor+Extension.swift in Sources */, + E07230522829FDF900AF3E16 /* BaseTarget.swift in Sources */, + E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */, + E072304F2829FDF900AF3E16 /* APIError.swift in Sources */, + E07230502829FDF900AF3E16 /* HTTPContentType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -659,6 +663,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -678,6 +683,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index 20dbb40..13fc39e 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -25,8 +25,12 @@ class StarbucksViewController: UITabBarController { payViewController.tabBarItem.title = "Pay" payViewController.tabBarItem.image = UIImage(named: "ic_temp") + let orderViewController = OrderViewController(viewModel: OrderViewModel()) + orderViewController.tabBarItem.title = "Order" + orderViewController.tabBarItem.image = UIImage(named: "ic_temp") + viewControllers = [ - homeViewController, payViewController + homeViewController, payViewController, orderViewController ] } } From 255ab1a9c606b777db4140c0566e3a9b0cb1ecc1 Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Tue, 10 May 2022 12:12:28 +0900 Subject: [PATCH 12/57] =?UTF-8?q?[STAR-11]=20feat:=20categoryButtons=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/.DS_Store | Bin 0 -> 8196 bytes Starbucks/Starbucks.xcodeproj/project.pbxproj | 2 + .../Present/Order/OrderViewController.swift | 65 ++++++++++++++---- 3 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 Starbucks/.DS_Store diff --git a/Starbucks/.DS_Store b/Starbucks/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..20dfd670066d04c0c0d87216f7c1975cc0c2a19a GIT binary patch literal 8196 zcmeHM&ubGw6n;~y-PmfkHqllQ7IP6S+Ey!wh%v2b5ozs~+TxG6yP2kIH#=c>(`ZW} zcfI%r_&4+{UOkB5!HX9W{{#h3p7hO+)R>q<5k%@dnE7UB-uHIr+nw2c2>_6MwKxVa z1ONsWj%F`bzcD_}OPh76o&$&k`2aoyK&J!dxj~z^?$8Qo1+)TM0j+>m;9pPx-`U)Z z4)1+As%x!)R^Y!>fbS0$7LGZEb%|o@z)GY5h~3yN4BMCo2#%#Nr?4(jP%);=9*Ck6 zm0}P{$8n3hBjyy=B}zIFNe80NOjL$K#MzN&33nhliMrMbXa(98;JJGb%mRZVEH~%x z%*7GDn66+s{aqNg0Q!FQhSQkyGw@(l=Kb5etrlLl;>ExEk>j2G+*(|50}=0qTM7I) z86AC3y7qMM-PdFE7`?_ldxh2P#7U}g-ihw;qj|;xx6yXo1)tUmW_pr^iQ|Vf5(K|U z(dF)KKP<9Zo>jwQEV#BDFj7XUU=D9=jE#;@j93#B{>(iO<5NrqQLlF$z7M(DHP+%917_hK}ecV3< zHYDIc1TIwhDuLGLpFcv)>ua!`fotp$a`_siP{CLX9=5EaZ!J+W9i!Bc9r5eHNDf9- zzt=U^Z5O}t=Ihx(q@Xlrd#qf+2x#OTRy$yq+0>L;@5YBWUniH+ulU}xqX@-6j8Iu= zo`Hp@*QOQNr3&nk^BUs$|JBaF|L@Y->uG5Pv;tfK(miEQ<$0rn9z55!usp!Rjr-Ll y3JO*t9Y+-DIO6snhS;{ym2+|m>k_dC?XUk3AilxW7ggK0kbeJ*o1&{-ufPxNAz|zQ literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index dc70c77..93433f8 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -617,6 +617,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -646,6 +647,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift index 88521fd..c58c29d 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift @@ -21,11 +21,28 @@ class OrderViewController: UIViewController { return label }() - private let button: UIButton = { - let button = UIButton() - button.backgroundColor = .red - button.setTitle("asd", for: .normal) - return button + private let categoryView: UIView = { + let view = UIView() + view.layer.borderWidth = 1 + view.layer.borderColor = UIColor.systemGray.cgColor + return view + }() + + private let categoryStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .equalSpacing + stackView.spacing = 10 + return stackView + }() + + private let categoryButtons: [UIButton] = { + Category.allCases.map { + let button = UIButton() + button.setTitle($0.name, for: .normal) + button.setTitleColor(.systemGray, for: .normal) + return button + } }() private let viewModel: OrderViewModelProtocol @@ -54,10 +71,6 @@ class OrderViewController: UIViewController { print($0) }) .disposed(by: disposeBag) - - button.rx.tap - .bind(to: viewModel.action().viewDidLoad) - .disposed(by: disposeBag) } private func attribute() { @@ -66,17 +79,43 @@ class OrderViewController: UIViewController { private func layout() { view.addSubview(orderLabel) - view.addSubview(button) + view.addSubview(categoryView) + categoryView.addSubview(categoryStackView) + + categoryButtons.forEach { + categoryStackView.addArrangedSubview($0) + } orderLabel.snp.makeConstraints { $0.top.equalToSuperview().offset(80) $0.leading.trailing.equalToSuperview().inset(20) } - button.snp.makeConstraints { - $0.width.height.equalTo(100) - $0.top.equalTo(orderLabel.snp.bottom).offset(20) + categoryView.snp.makeConstraints { + $0.top.equalTo(orderLabel.snp.bottom).offset(40) + $0.leading.trailing.equalToSuperview().inset(-1) + $0.bottom.equalTo(categoryStackView) + } + + categoryStackView.snp.makeConstraints { + $0.top.equalToSuperview().offset(2) $0.leading.equalToSuperview().offset(20) + $0.height.equalTo(45) + } + } +} + +enum Category: CaseIterable { + case beverage, food, product + + var name: String { + switch self { + case .beverage: + return "음료" + case .food: + return "푸드" + case .product: + return "상품" } } } From d94c676cd6a2d9cb55c001de414f4aacb9b7aea6 Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Tue, 10 May 2022 17:01:27 +0900 Subject: [PATCH 13/57] =?UTF-8?q?[STAR-9]=20feat:=20OrderTableView,=20Orde?= =?UTF-8?q?rTableViewCell=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 16 ++++ Starbucks/Starbucks/Resource/mockImage.png | Bin 0 -> 122768 bytes .../Present/Order/OrderTableViewCell.swift | 82 ++++++++++++++++++ .../Order/OrderTableViewDataSource.swift | 30 +++++++ .../Order/OrderTableViewDelegate.swift | 15 ++++ .../Present/Order/OrderViewController.swift | 19 ++++ 6 files changed, 162 insertions(+) create mode 100644 Starbucks/Starbucks/Resource/mockImage.png create mode 100644 Starbucks/Starbucks/Sources/Present/Order/OrderTableViewCell.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index 93433f8..829623f 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 1E89A091282A2D6600CD7625 /* OrderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A090282A2D6600CD7625 /* OrderTableViewCell.swift */; }; + 1E89A093282A2D7400CD7625 /* OrderTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A092282A2D7400CD7625 /* OrderTableViewDataSource.swift */; }; + 1E89A095282A300000CD7625 /* mockImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E89A094282A300000CD7625 /* mockImage.png */; }; + 1E89A09B282A520200CD7625 /* OrderTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A09A282A520200CD7625 /* OrderTableViewDelegate.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; @@ -40,6 +44,10 @@ /* Begin PBXFileReference section */ 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.debug.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.debug.xcconfig"; sourceTree = ""; }; + 1E89A090282A2D6600CD7625 /* OrderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTableViewCell.swift; sourceTree = ""; }; + 1E89A092282A2D7400CD7625 /* OrderTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTableViewDataSource.swift; sourceTree = ""; }; + 1E89A094282A300000CD7625 /* mockImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mockImage.png; sourceTree = ""; }; + 1E89A09A282A520200CD7625 /* OrderTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTableViewDelegate.swift; sourceTree = ""; }; 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -131,6 +139,7 @@ E01B528F2828A0C3009918AE /* Resource */ = { isa = PBXGroup; children = ( + 1E89A094282A300000CD7625 /* mockImage.png */, E01B528228289D18009918AE /* Assets.xcassets */, ); path = Resource; @@ -172,6 +181,9 @@ isa = PBXGroup; children = ( E07230322829FDF900AF3E16 /* OrderViewController.swift */, + 1E89A09A282A520200CD7625 /* OrderTableViewDelegate.swift */, + 1E89A092282A2D7400CD7625 /* OrderTableViewDataSource.swift */, + 1E89A090282A2D6600CD7625 /* OrderTableViewCell.swift */, E07230332829FDF900AF3E16 /* OrderViewModel.swift */, ); path = Order; @@ -334,6 +346,7 @@ buildActionMask = 2147483647; files = ( E01B528328289D18009918AE /* Assets.xcassets in Resources */, + 1E89A095282A300000CD7625 /* mockImage.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -452,6 +465,8 @@ files = ( E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */, E072304C2829FDF900AF3E16 /* PayViewController.swift in Sources */, + 1E89A093282A2D7400CD7625 /* OrderTableViewDataSource.swift in Sources */, + 1E89A091282A2D6600CD7625 /* OrderTableViewCell.swift in Sources */, E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */, E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */, E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */, @@ -465,6 +480,7 @@ E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */, E072304F2829FDF900AF3E16 /* APIError.swift in Sources */, E07230502829FDF900AF3E16 /* HTTPContentType.swift in Sources */, + 1E89A09B282A520200CD7625 /* OrderTableViewDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Starbucks/Starbucks/Resource/mockImage.png b/Starbucks/Starbucks/Resource/mockImage.png new file mode 100644 index 0000000000000000000000000000000000000000..98b8a88db20979cbccb470c41d1ce0dac9d5ce6b GIT binary patch literal 122768 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 literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewCell.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewCell.swift new file mode 100644 index 0000000..722c40a --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewCell.swift @@ -0,0 +1,82 @@ +// +// OrderTableViewCell.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import UIKit + +class OrderTableViewCell: UITableViewCell { + + static let identifier = "OrderTableViewCell" + + private let menuImageView: UIImageView = { + let image = UIImage(named: "mockImage.png") + let imageView = UIImageView(image: image) + imageView.layer.cornerRadius = 40 + imageView.clipsToBounds = true + return imageView + }() + + private let menuNameStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.spacing = 3 + return stackView + }() + + private let mainNameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 16, weight: .bold) + return label + }() + + private let subNameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12) + label.textColor = .systemGray + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func layout() { + contentView.addSubview(menuImageView) + contentView.addSubview(menuNameStackView) + menuNameStackView.addArrangedSubview(mainNameLabel) + menuNameStackView.addArrangedSubview(subNameLabel) + + menuImageView.snp.makeConstraints { + $0.leading.equalToSuperview().offset(30) + $0.centerY.equalToSuperview() + $0.width.height.equalTo(80) + } + + menuNameStackView.snp.makeConstraints { + $0.leading.equalTo(menuImageView.snp.trailing).offset(20) + $0.centerY.equalTo(menuImageView.snp.centerY) + } + } + + func setMenuName(text: String) { + mainNameLabel.text = text + } + + func setSubName(text: String) { + subNameLabel.text = text + } + + // func setMenuName(texts: [String: String]) { + // + // } +} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift new file mode 100644 index 0000000..2b8e24e --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift @@ -0,0 +1,30 @@ +// +// OrderTableViewDataSource.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import UIKit + +class OrderTableViewDataSource: NSObject, UITableViewDataSource { + + let mainLanguage = ["콜드브루", "블론드", "에스프레소"] + let subLanguage = ["Cold Brew", "Blonde Coffe", "Young-Jin"] + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return mainLanguage.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "OrderTableViewCell", for: indexPath) + as? OrderTableViewCell else { + return UITableViewCell() + } + + cell.setMenuName(text: mainLanguage[indexPath.row]) + cell.setSubName(text: subLanguage[indexPath.row]) + return cell + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift new file mode 100644 index 0000000..0178e5a --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift @@ -0,0 +1,15 @@ +// +// OrderTableViewDelegate.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import UIKit + +class OrderTableViewDelegate: NSObject, UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + print(indexPath) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift index c58c29d..e72c3c3 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift @@ -13,6 +13,9 @@ import UIKit class OrderViewController: UIViewController { + private let tableView = UITableView() + private let tableViewDataSource = OrderTableViewDataSource() + private let orderLabel: UILabel = { let label = UILabel() label.text = "Order" @@ -75,6 +78,7 @@ class OrderViewController: UIViewController { private func attribute() { view.backgroundColor = .systemBackground + configureTableView() } private func layout() { @@ -82,6 +86,8 @@ class OrderViewController: UIViewController { view.addSubview(categoryView) categoryView.addSubview(categoryStackView) + view.addSubview(tableView) + categoryButtons.forEach { categoryStackView.addArrangedSubview($0) } @@ -102,6 +108,19 @@ class OrderViewController: UIViewController { $0.leading.equalToSuperview().offset(20) $0.height.equalTo(45) } + + tableView.snp.makeConstraints { + $0.top.equalTo(categoryView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(view.safeAreaLayoutGuide) + } + } + + private func configureTableView() { + tableView.rowHeight = 100 + tableView.separatorStyle = .none + tableView.register(OrderTableViewCell.self, forCellReuseIdentifier: OrderTableViewCell.identifier) + tableView.dataSource = tableViewDataSource } } From d63025deb12e58b3e694eb6ddbe299d3b03bf65c Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Tue, 10 May 2022 18:09:12 +0900 Subject: [PATCH 14/57] =?UTF-8?q?[STAR-12]=20feat:=20OrderVC=EC=97=90?= =?UTF-8?q?=EC=84=9C=20cell=20=EC=84=A0=ED=83=9D=EC=8B=9C,=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=20cell=EC=97=90=20=EB=A7=9E=EB=8A=94=20detailVC?= =?UTF-8?q?=EA=B0=80=20push=EB=90=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 4 ++ .../Order/DetailOrderViewController.swift | 30 ++++++++++++++ .../Order/OrderTableViewDataSource.swift | 41 +++++++++++++++++-- .../Order/OrderTableViewDelegate.swift | 21 +++++++++- .../Present/Order/OrderViewController.swift | 21 ++++++++++ .../TabBar/StarbucksViewController.swift | 3 +- 6 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Present/Order/DetailOrderViewController.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index 829623f..2a8ac0c 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 1E89A093282A2D7400CD7625 /* OrderTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A092282A2D7400CD7625 /* OrderTableViewDataSource.swift */; }; 1E89A095282A300000CD7625 /* mockImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E89A094282A300000CD7625 /* mockImage.png */; }; 1E89A09B282A520200CD7625 /* OrderTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A09A282A520200CD7625 /* OrderTableViewDelegate.swift */; }; + 1E89A0A0282A628C00CD7625 /* DetailOrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E89A09F282A628C00CD7625 /* DetailOrderViewController.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; @@ -48,6 +49,7 @@ 1E89A092282A2D7400CD7625 /* OrderTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTableViewDataSource.swift; sourceTree = ""; }; 1E89A094282A300000CD7625 /* mockImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mockImage.png; sourceTree = ""; }; 1E89A09A282A520200CD7625 /* OrderTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTableViewDelegate.swift; sourceTree = ""; }; + 1E89A09F282A628C00CD7625 /* DetailOrderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailOrderViewController.swift; sourceTree = ""; }; 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -185,6 +187,7 @@ 1E89A092282A2D7400CD7625 /* OrderTableViewDataSource.swift */, 1E89A090282A2D6600CD7625 /* OrderTableViewCell.swift */, E07230332829FDF900AF3E16 /* OrderViewModel.swift */, + 1E89A09F282A628C00CD7625 /* DetailOrderViewController.swift */, ); path = Order; sourceTree = ""; @@ -470,6 +473,7 @@ E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */, E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */, E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */, + 1E89A0A0282A628C00CD7625 /* DetailOrderViewController.swift in Sources */, E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */, E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */, E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/Present/Order/DetailOrderViewController.swift b/Starbucks/Starbucks/Sources/Present/Order/DetailOrderViewController.swift new file mode 100644 index 0000000..9cb4850 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Order/DetailOrderViewController.swift @@ -0,0 +1,30 @@ +// +// DetailOrderViewController.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import UIKit + +class DetailOrderViewController: UIViewController { + + init(title: String) { + super.init(nibName: nil, bundle: nil) + self.title = title + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + attribute() + } + + private func attribute() { + view.backgroundColor = .systemBackground + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift index 2b8e24e..c1a601e 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift @@ -6,15 +6,37 @@ // import UIKit +import RxSwift +import RxRelay + +struct Menu { + var mainLanguageName: String + var subLanguageName: String +} class OrderTableViewDataSource: NSObject, UITableViewDataSource { let mainLanguage = ["콜드브루", "블론드", "에스프레소"] let subLanguage = ["Cold Brew", "Blonde Coffe", "Young-Jin"] - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let receiver = PublishRelay() + let sender = PublishRelay() + let disposeBag = DisposeBag() + + override init() { + super.init() - return mainLanguage.count + receiver + .compactMap { [weak self] in + self?.mainLanguage[$0] + } + .bind(to: sender) + .disposed(by: disposeBag) + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let rowCount = mainLanguage.count + return rowCount } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -23,8 +45,19 @@ class OrderTableViewDataSource: NSObject, UITableViewDataSource { return UITableViewCell() } - cell.setMenuName(text: mainLanguage[indexPath.row]) - cell.setSubName(text: subLanguage[indexPath.row]) + return configure(cell: cell, index: indexPath.row) + } + + private func configure(cell: OrderTableViewCell, index: Int) -> UITableViewCell { + setSelectedBackgroundView(of: cell) + cell.setMenuName(text: mainLanguage[index]) + cell.setSubName(text: subLanguage[index]) return cell } + + private func setSelectedBackgroundView(of cell: UITableViewCell) { + let view = UIView() + view.backgroundColor = .systemBackground + cell.selectedBackgroundView = view + } } diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift index 0178e5a..fda8f84 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift @@ -6,10 +6,29 @@ // import UIKit +import RxSwift +import RxRelay + +protocol CellSelectionDetectable: AnyObject { + func didSelectCell(indexPath: IndexPath) +} class OrderTableViewDelegate: NSObject, UITableViewDelegate { +// weak var delegate: CellSelectionDetectable? + + let selectedCellIndex = PublishSubject() + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - print(indexPath) + + selectedCellIndex + .onNext(indexPath.row) + //delegate가 indexPath를 발행해줌 + //dataSource가 indexPath를 받아야함 + //dataSource가 VC에게 indexPath에 있는 cell의 정보를 발행해줌 + //VC가 그 정보를 받아서 NavigationVC를 Push + +// print(indexPath.row) +// delegate?.didSelectCell(indexPath: indexPath) } } diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift index e72c3c3..f5f2c3e 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift @@ -15,6 +15,7 @@ class OrderViewController: UIViewController { private let tableView = UITableView() private let tableViewDataSource = OrderTableViewDataSource() + private let tableViewDe1232131legate32132213213213 = OrderTableViewDelegate() private let orderLabel: UILabel = { let label = UILabel() @@ -57,6 +58,8 @@ class OrderViewController: UIViewController { bind() attribute() layout() + +// tableViewDe1232131legate32132213213213.delegate = self } @available(*, unavailable) @@ -74,6 +77,17 @@ class OrderViewController: UIViewController { print($0) }) .disposed(by: disposeBag) + + tableViewDe1232131legate32132213213213.selectedCellIndex + .bind(to: tableViewDataSource.receiver) + .disposed(by: disposeBag) + + tableViewDataSource.sender + .bind { + let detailOrderView = DetailOrderViewController(title: $0) + self.navigationController?.pushViewController(detailOrderView, animated: true) + } + .disposed(by: disposeBag) } private func attribute() { @@ -121,6 +135,13 @@ class OrderViewController: UIViewController { tableView.separatorStyle = .none tableView.register(OrderTableViewCell.self, forCellReuseIdentifier: OrderTableViewCell.identifier) tableView.dataSource = tableViewDataSource + tableView.delegate = tableViewDe1232131legate32132213213213 + } +} + +extension OrderViewController: CellSelectionDetectable { + func didSelectCell(indexPath: IndexPath) { + print(tableViewDataSource.mainLanguage[indexPath.row]) } } diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index 13fc39e..443130b 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -25,7 +25,8 @@ class StarbucksViewController: UITabBarController { payViewController.tabBarItem.title = "Pay" payViewController.tabBarItem.image = UIImage(named: "ic_temp") - let orderViewController = OrderViewController(viewModel: OrderViewModel()) + let rootVC = OrderViewController(viewModel: OrderViewModel()) + let orderViewController = UINavigationController(rootViewController: rootVC) orderViewController.tabBarItem.title = "Order" orderViewController.tabBarItem.image = UIImage(named: "ic_temp") From 3b455a765e9ca3ac1b238cbf0325467bb6e9a14e Mon Sep 17 00:00:00 2001 From: shingha Date: Tue, 10 May 2022 19:44:19 +0900 Subject: [PATCH 15/57] =?UTF-8?q?[STAR-10]=20feature:=20=EB=84=A4=ED=8A=B8?= =?UTF-8?q?=EC=9B=8C=ED=81=AC=20=ED=86=B5=EC=8B=A0=20=ED=99=95=EC=9D=B8(?= =?UTF-8?q?=20=ED=99=88=ED=99=94=EB=A9=B4=20=EB=8D=B0=EC=9D=B4=ED=84=B0,?= =?UTF-8?q?=20=ED=99=88=ED=99=94=EB=A9=B4=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 82 ++++++++++++++++++- .../Starbucks/Sources/API/Base/APIError.swift | 20 ++++- .../Sources/API/Base/BaseTarget.swift | 2 +- .../Starbucks/Sources/API/Base/Provider.swift | 43 +++++++++- .../Starbucks/Sources/API/Base/Response.swift | 82 +++++++++++++++++++ .../Sources/API/StarbucksTarget.swift | 61 ++++++++++++++ Starbucks/Starbucks/Sources/Common/Log.swift | 40 +++++++++ .../Sources/Extension/Result+Extension.swift | 37 +++++++++ .../Sources/Model/StarbucksEntity.swift | 59 +++++++++++++ .../Present/Home/HomeViewController.swift | 6 +- .../Sources/Present/Home/HomeViewModel.swift | 38 ++++++++- .../Present/Order/OrderViewModel.swift | 1 + .../Repository/NetworkRepository.swift | 12 +++ .../Starbucks/StarbucksRepository.swift | 14 ++++ .../Starbucks/StarbucksRepositoryImpl.swift | 23 ++++++ .../API/StarbuckTargetTest.swift | 33 ++++++++ 16 files changed, 542 insertions(+), 11 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/API/Base/Response.swift create mode 100644 Starbucks/Starbucks/Sources/API/StarbucksTarget.swift create mode 100644 Starbucks/Starbucks/Sources/Common/Log.swift create mode 100644 Starbucks/Starbucks/Sources/Extension/Result+Extension.swift create mode 100644 Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift create mode 100644 Starbucks/Starbucks/Sources/Repository/NetworkRepository.swift create mode 100644 Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift create mode 100644 Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift create mode 100644 Starbucks/StarbucksTests/API/StarbuckTargetTest.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index dc70c77..ba9ea0a 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -25,6 +25,15 @@ E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230442829FDF900AF3E16 /* HTTPMethod.swift */; }; E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230452829FDF900AF3E16 /* AppDelegate.swift */; }; E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230462829FDF900AF3E16 /* SceneDelegate.swift */; }; + E0723058282A13C900AF3E16 /* StarbuckTargetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */; }; + E072305A282A13F300AF3E16 /* StarbucksTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723059282A13F300AF3E16 /* StarbucksTarget.swift */; }; + E072305D282A178700AF3E16 /* NetworkRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072305C282A178700AF3E16 /* NetworkRepository.swift */; }; + E0723060282A17B800AF3E16 /* StarbucksRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072305F282A17B800AF3E16 /* StarbucksRepository.swift */; }; + E0723062282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723061282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift */; }; + E0723064282A1A5000AF3E16 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723063282A1A5000AF3E16 /* Response.swift */; }; + E0723067282A3C2300AF3E16 /* StarbucksEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723066282A3C2300AF3E16 /* StarbucksEntity.swift */; }; + E0723337282A71B700AF3E16 /* Result+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723336282A71B700AF3E16 /* Result+Extension.swift */; }; + E072333A282A724500AF3E16 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723339282A724500AF3E16 /* Log.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -62,6 +71,15 @@ E07230442829FDF900AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; E07230452829FDF900AF3E16 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E07230462829FDF900AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbuckTargetTest.swift; sourceTree = ""; }; + E0723059282A13F300AF3E16 /* StarbucksTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTarget.swift; sourceTree = ""; }; + E072305C282A178700AF3E16 /* NetworkRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRepository.swift; sourceTree = ""; }; + E072305F282A17B800AF3E16 /* StarbucksRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksRepository.swift; sourceTree = ""; }; + E0723061282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksRepositoryImpl.swift; sourceTree = ""; }; + E0723063282A1A5000AF3E16 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; + E0723066282A3C2300AF3E16 /* StarbucksEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksEntity.swift; sourceTree = ""; }; + E0723336282A71B700AF3E16 /* Result+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extension.swift"; sourceTree = ""; }; + E0723339282A724500AF3E16 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -147,9 +165,12 @@ E072302F2829FDF900AF3E16 /* Sources */ = { isa = PBXGroup; children = ( + E0723065282A3C1700AF3E16 /* Model */, + E072305B282A177800AF3E16 /* Repository */, + E072303E2829FDF900AF3E16 /* API */, E07230302829FDF900AF3E16 /* Present */, + E0723338282A723F00AF3E16 /* Common */, E072303C2829FDF900AF3E16 /* Extension */, - E072303E2829FDF900AF3E16 /* API */, E07230452829FDF900AF3E16 /* AppDelegate.swift */, E07230462829FDF900AF3E16 /* SceneDelegate.swift */, ); @@ -206,6 +227,7 @@ isa = PBXGroup; children = ( E072303D2829FDF900AF3E16 /* UIColor+Extension.swift */, + E0723336282A71B700AF3E16 /* Result+Extension.swift */, ); path = Extension; sourceTree = ""; @@ -214,6 +236,7 @@ isa = PBXGroup; children = ( E072303F2829FDF900AF3E16 /* Base */, + E0723059282A13F300AF3E16 /* StarbucksTarget.swift */, ); path = API; sourceTree = ""; @@ -221,18 +244,62 @@ E072303F2829FDF900AF3E16 /* Base */ = { isa = PBXGroup; children = ( - E07230402829FDF900AF3E16 /* APIError.swift */, - E07230412829FDF900AF3E16 /* HTTPContentType.swift */, E07230422829FDF900AF3E16 /* Provider.swift */, E07230432829FDF900AF3E16 /* BaseTarget.swift */, E07230442829FDF900AF3E16 /* HTTPMethod.swift */, + E07230412829FDF900AF3E16 /* HTTPContentType.swift */, + E07230402829FDF900AF3E16 /* APIError.swift */, + E0723063282A1A5000AF3E16 /* Response.swift */, ); path = Base; sourceTree = ""; }; + E0723056282A13AB00AF3E16 /* API */ = { + isa = PBXGroup; + children = ( + E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */, + ); + path = API; + sourceTree = ""; + }; + E072305B282A177800AF3E16 /* Repository */ = { + isa = PBXGroup; + children = ( + E072305E282A17A700AF3E16 /* Starbucks */, + E072305C282A178700AF3E16 /* NetworkRepository.swift */, + ); + path = Repository; + sourceTree = ""; + }; + E072305E282A17A700AF3E16 /* Starbucks */ = { + isa = PBXGroup; + children = ( + E072305F282A17B800AF3E16 /* StarbucksRepository.swift */, + E0723061282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift */, + ); + path = Starbucks; + sourceTree = ""; + }; + E0723065282A3C1700AF3E16 /* Model */ = { + isa = PBXGroup; + children = ( + E0723066282A3C2300AF3E16 /* StarbucksEntity.swift */, + ); + path = Model; + sourceTree = ""; + }; + E0723338282A723F00AF3E16 /* Common */ = { + isa = PBXGroup; + children = ( + E0723339282A724500AF3E16 /* Log.swift */, + ); + path = Common; + sourceTree = ""; + }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { isa = PBXGroup; children = ( + E0723056282A13AB00AF3E16 /* API */, E08BB6AE28294347005ADEFA /* StarbucksTests.swift */, ); path = StarbucksTests; @@ -451,20 +518,28 @@ buildActionMask = 2147483647; files = ( E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */, + E072333A282A724500AF3E16 /* Log.swift in Sources */, E072304C2829FDF900AF3E16 /* PayViewController.swift in Sources */, E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */, E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */, + E0723064282A1A5000AF3E16 /* Response.swift in Sources */, E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */, E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */, E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */, E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */, + E0723060282A17B800AF3E16 /* StarbucksRepository.swift in Sources */, E072304A2829FDF900AF3E16 /* HomeViewController.swift in Sources */, + E072305A282A13F300AF3E16 /* StarbucksTarget.swift in Sources */, + E0723337282A71B700AF3E16 /* Result+Extension.swift in Sources */, E07230512829FDF900AF3E16 /* Provider.swift in Sources */, E072304E2829FDF900AF3E16 /* UIColor+Extension.swift in Sources */, + E0723062282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, E07230522829FDF900AF3E16 /* BaseTarget.swift in Sources */, E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */, + E0723067282A3C2300AF3E16 /* StarbucksEntity.swift in Sources */, E072304F2829FDF900AF3E16 /* APIError.swift in Sources */, E07230502829FDF900AF3E16 /* HTTPContentType.swift in Sources */, + E072305D282A178700AF3E16 /* NetworkRepository.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -473,6 +548,7 @@ buildActionMask = 2147483647; files = ( E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */, + E0723058282A13C900AF3E16 /* StarbuckTargetTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Starbucks/Starbucks/Sources/API/Base/APIError.swift b/Starbucks/Starbucks/Sources/API/Base/APIError.swift index 3726b02..f79d737 100644 --- a/Starbucks/Starbucks/Sources/API/Base/APIError.swift +++ b/Starbucks/Starbucks/Sources/API/Base/APIError.swift @@ -8,5 +8,23 @@ import Foundation enum APIError: Error { -case custom(message: String, debugMessage: String) + case custom(message: String, debugMessage: String) + case jsonMapping(response: Response) + case objectMapping(error: Error, response: Response) + case underlying(error: Swift.Error, response: Response?) + case unowned +} + +extension APIError { + var statusCode: Int { + switch self { + case .jsonMapping(let response), + .objectMapping(_, let response): + return response.statusCode + case .underlying(_, let response): + return response?.statusCode ?? -9999 + case .custom, .unowned: + return -9999 + } + } } diff --git a/Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift b/Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift index 9df8c0c..bdd3939 100644 --- a/Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift +++ b/Starbucks/Starbucks/Sources/API/Base/BaseTarget.swift @@ -9,7 +9,7 @@ import Foundation protocol BaseTarget { var baseURL: URL? { get } - var path: String { get } + var path: String? { get } var parameter: [String: Any]? { get } var method: HTTPMethod { get } var content: HTTPContentType { get } diff --git a/Starbucks/Starbucks/Sources/API/Base/Provider.swift b/Starbucks/Starbucks/Sources/API/Base/Provider.swift index bee5ea2..4d22382 100644 --- a/Starbucks/Starbucks/Sources/API/Base/Provider.swift +++ b/Starbucks/Starbucks/Sources/API/Base/Provider.swift @@ -11,10 +11,15 @@ import RxSwift class Provider { private static func createRequest(_ target: Target) -> URLRequest? { - guard let url = target.baseURL?.appendingPathComponent(target.path) else { + guard let baseUrl = target.baseURL else { return nil } + var url = baseUrl + if let path = target.path { + url = baseUrl.appendingPathComponent(path) + } + var request = URLRequest(url: url) request.httpMethod = target.method.value target.header?.forEach { key, value in @@ -38,7 +43,39 @@ class Provider { return request } - func request(_ target: Target) { -// Single.crea + func request(_ target: Target) -> Single> { + Single.create { observer in + guard let request = Self.createRequest(target) else { + let error = APIError.custom(message: "", debugMessage: "Request가 제대로 생성되지 않았습니다.") + observer(.success(.failure(error))) + return Disposables.create { AF.session.invalidateAndCancel() } + } + + let dataRequest: DataRequest = AF.request(request) + + dataRequest + .response { dataResponse in + switch ( dataResponse.response, dataResponse.data, dataResponse.error) { + case let (.some(urlResponse), data, .none): + let response = Response(statusCode: urlResponse.statusCode, data: data ?? Data(), request: request, response: urlResponse) + observer(.success(.success(response))) + + case let (.some(urlResponse), _, .some(error)): + let response = Response(statusCode: urlResponse.statusCode, data: Data(), request: request, response: urlResponse) + let apiError = APIError.underlying(error: error, response: response) + observer(.success(.failure(apiError))) + + case let (_, _, .some(error)): + let apiError = APIError.underlying(error: error, response: nil) + observer(.success(.failure(apiError))) + + default: + let apiError = APIError.underlying(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil), response: nil) + observer(.success(.failure(apiError))) + } + } + + return Disposables.create { AF.session.invalidateAndCancel() } + } } } diff --git a/Starbucks/Starbucks/Sources/API/Base/Response.swift b/Starbucks/Starbucks/Sources/API/Base/Response.swift new file mode 100644 index 0000000..5ebbd20 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/Response.swift @@ -0,0 +1,82 @@ +// +// Response.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation +import RxSwift + +class Response { + let statusCode: Int + let data: Data + let request: URLRequest? + let response: HTTPURLResponse? + + init(statusCode: Int, data: Data, request: URLRequest? = nil, response: HTTPURLResponse? = nil) { + self.statusCode = statusCode + self.data = data + self.request = request + self.response = response + } +} + +extension Response { + func map(_ type: D.Type, using decoder: JSONDecoder = JSONDecoder()) throws -> D { + if data.count < 1 { + throw APIError.jsonMapping(response: self) + } + do { + return try decoder.decode(D.self, from: data) + } catch { + throw APIError.objectMapping(error: error, response: self) + } + } +} + +extension PrimitiveSequence where Trait == SingleTrait, Element == Result { + + func map(_ type: T.Type) -> Single> { + let response = filterSuccessStatusCode() + .map { result -> Result in + result.flatMap { response in + do { + let item = try response.map(type) + return .success(item) + } catch { + let apiError = (error as? APIError) ?? APIError.underlying(error: error, response: response) + return .failure(apiError) + } + } + } + + return response.flatMap { result in .just(result) } + .do(onSuccess: { result in + if case .failure(let error) = result { + Log.error("APIError : \(error)") + } + }) + } + + private func filterSuccessStatusCode() -> Single> { + self.map { result -> Result in + result.flatMap { response in + if (200...299).contains(response.statusCode) { + return .success(response) + } + + return .failure(APIError.unowned) +// let apiError = (error as? APIError) ?? APIError.underlying(error: error, response: response) +// return .failure(apiError) +// do { +// let serverReponse = try response.map(APIError.ServerResponse.self, atKeyPath: "error") +// return .failure(APIError.serverDefined(response: response, serverResponse: serverReponse)) +// } catch { +// let apiError = (error as? APIError) ?? APIError.underlying(error: error, response: response) +// return .failure(apiError) +// } + } + } + } +} diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift new file mode 100644 index 0000000..9c142c0 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -0,0 +1,61 @@ +// +// StarbucksTarget.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +enum StarbucksTarget: BaseTarget { + case requestHome + case requestEvent +} + +extension StarbucksTarget { + + var baseURL: URL? { + switch self { + case .requestHome: + return URL(string: "https://api.codesquad.kr/starbuckst") + case .requestEvent: + return URL(string: "https://www.starbucks.co.kr") + } + } + + var path: String? { + switch self { + case .requestHome: + return nil + case .requestEvent: + return "/whats_new/getIngList.do" + } + } + + var parameter: [String : Any]? { + switch self { + case .requestHome: + return nil + case .requestEvent: + return ["MENU_CD":"all"] + } + } + + var method: HTTPMethod { + switch self { + case .requestHome: + return .get + case .requestEvent: + return .post + } + } + + var content: HTTPContentType { + switch self { + case .requestHome: + return .json + case .requestEvent: + return .urlencode + } + } +} diff --git a/Starbucks/Starbucks/Sources/Common/Log.swift b/Starbucks/Starbucks/Sources/Common/Log.swift new file mode 100644 index 0000000..b31b9bf --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/Log.swift @@ -0,0 +1,40 @@ +// +// Log.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation +import OSLog + +enum Log { + + //Error케이스와 유사하지만, 에러 설명이 긴 경우 + static func info(_ message: String) { + printLog(logType: .info, message: message) + } + + //개발 환경에서의 간단한 로깅 (mac의 '콘솔'앱에는 찍히지 않고 xcode console에만 표출) + static func debug(_ message: String) { + printLog(logType: .debug, message: message) + } + + static func fault(_ message: String) { + printLog(logType: .fault, message: message) + } + + //문제 해결을 위한 level + static func print(_ message: String) { + printLog(logType: .default, message: message) + } + + //Info케이스와 유사하지만, 간단한 에러인 경우 + static func error(_ message: String) { + printLog(logType: .error, message: message) + } + + private static func printLog(logType: OSLogType, message: String) { + os_log(logType, log: .default, "\(message)") + } +} diff --git a/Starbucks/Starbucks/Sources/Extension/Result+Extension.swift b/Starbucks/Starbucks/Sources/Extension/Result+Extension.swift new file mode 100644 index 0000000..2230d11 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Extension/Result+Extension.swift @@ -0,0 +1,37 @@ +// +// Result+Extension.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +extension Result { + + var value: Success? { + guard case .success(let value) = self else { + return nil + } + return value + } + + var error: Failure? { + guard case .failure(let error) = self else { + return nil + } + return error + } + + var isSuccess: Bool { + if case .success = self { + return true + } else { + return false + } + } + + var isFailure: Bool { + !isSuccess + } +} diff --git a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift new file mode 100644 index 0000000..10f695d --- /dev/null +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -0,0 +1,59 @@ +// +// HomeModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +struct StarbucksEntity {} + +extension StarbucksEntity { + struct Home: Decodable { + let displayName: String + let yourRecommand: Recommand + let mainEvent: MainEvent + let nowRecommand: Recommand + + enum CodingKeys: String, CodingKey { + case displayName = "display-name" + case yourRecommand = "your-recommand" + case mainEvent = "main-event" + case nowRecommand = "now-recommand" + } + } + + struct Recommand: Decodable { + let products: [String]? + } + + struct MainEvent: Decodable { + let imageUploadPath: URL + let thumbnail: String + + enum CodingKeys: String, CodingKey { + case imageUploadPath = "img_UPLOAD_PATH" + case thumbnail = "mob_THUM" + } + } +} + +extension StarbucksEntity { + + struct HomeEvent: Decodable { + let list: [Event] + } + + struct Event: Decodable { + let title: String + let imageUploadPath: URL + let thumbnail: String + + enum CodingKeys: String, CodingKey { + case title + case imageUploadPath = "img_UPLOAD_PATH" + case thumbnail = "mob_THUM" + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 74de6a2..e8b330e 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -27,7 +27,11 @@ class HomeViewController: UIViewController { private func bind() { rx.viewDidLoad - .bind(to: viewModel.action().loadHomeData) + .bind(to: viewModel.action().loadHome) + .disposed(by: disposeBag) + + rx.viewDidLoad + .bind(to: viewModel.action().loadEvent) .disposed(by: disposeBag) } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 11af130..8e65363 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -7,9 +7,11 @@ import Foundation import RxRelay +import RxSwift protocol HomeViewModelAction { - var loadHomeData: PublishRelay { get } + var loadHome: PublishRelay { get } + var loadEvent: PublishRelay { get } } protocol HomeViewModelState { @@ -25,10 +27,42 @@ typealias HomeViewModelProtocol = HomeViewModelBinding class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelState { func action() -> HomeViewModelAction { self } - let loadHomeData = PublishRelay() + let loadHome = PublishRelay() + let loadEvent = PublishRelay() func state() -> HomeViewModelState { self } + private var starbucksRepository = StarbucksRepositoryImpl() + + private let disposeBag = DisposeBag() + init() { + +// let requestHome = action().loadHome +// .flatMapLatest { [weak self] _ in +// self?.starbucksRepository.requestHome() ?? .never() +// } +// .share() +// +// requestHome +// .compactMap { $0.value } +// .bind(onNext: { +// print($0) +// }) +// .disposed(by: disposeBag) + + let requestEvent = action().loadEvent + .flatMapLatest { [weak self] _ in + self?.starbucksRepository.requestEvent() ?? .never() + } + .share() + + requestEvent + .compactMap { $0.value } + .bind(onNext: { + print($0) + }) + .disposed(by: disposeBag) + } } diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift index 8579bf9..d07dcd4 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift @@ -37,6 +37,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB let disposeBag = DisposeBag() init() { + viewDidLoad .map { "map test" } .bind(to: test) diff --git a/Starbucks/Starbucks/Sources/Repository/NetworkRepository.swift b/Starbucks/Starbucks/Sources/Repository/NetworkRepository.swift new file mode 100644 index 0000000..deb5062 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Repository/NetworkRepository.swift @@ -0,0 +1,12 @@ +// +// NetworkRepository.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +class NetworkRepository { + let provider = Provider() +} diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift new file mode 100644 index 0000000..a06383a --- /dev/null +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -0,0 +1,14 @@ +// +// StarbucksRepository.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation +import RxSwift + +protocol StarbucksRepository { + func requestHome() -> Single> + func requestEvent() -> Single> +} diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift new file mode 100644 index 0000000..5d5ddb8 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -0,0 +1,23 @@ +// +// StarbucksRepositoryImpl.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation +import RxSwift + +class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepository { + func requestHome() -> Single> { + provider + .request(.requestHome) + .map(StarbucksEntity.Home.self) + } + + func requestEvent() -> Single> { + provider + .request(.requestEvent) + .map(StarbucksEntity.HomeEvent.self) + } +} diff --git a/Starbucks/StarbucksTests/API/StarbuckTargetTest.swift b/Starbucks/StarbucksTests/API/StarbuckTargetTest.swift new file mode 100644 index 0000000..44a84b5 --- /dev/null +++ b/Starbucks/StarbucksTests/API/StarbuckTargetTest.swift @@ -0,0 +1,33 @@ +// +// StarbuckTargetTest.swift +// StarbucksTests +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation +import Nimble +import Quick +import XCTest + +@testable import Starbucks + +class BroadcastTargetTests: XCTestCase { + + var sut: StarbucksTarget! + + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func test_requestHome() { + sut = .requestHome + expect(self.sut.path).to(beNil()) + expect(self.sut.parameter).to(beNil()) + expect(self.sut.method).to(equal(.get)) + } +} From 9fb1de5e58142dee20b7f994716fcd7ceab72a80 Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Wed, 11 May 2022 00:16:26 +0900 Subject: [PATCH 16/57] =?UTF-8?q?[STAR-7]=20feat:=20Category=20Order=20Tab?= =?UTF-8?q?leView=20DataSource=20=EB=A5=BC=20ViewModel=20=EA=B3=BC=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/.DS_Store | Bin 6148 -> 8196 bytes Starbucks/Starbucks.xcodeproj/project.pbxproj | 64 ++++++++++++--- Starbucks/Starbucks/.DS_Store | Bin 0 -> 6148 bytes .../mockImage.imageset/Contents.json | 21 +++++ .../mockImage.imageset/mockImage.png | Bin 0 -> 122768 bytes Starbucks/Starbucks/Sources/.DS_Store | Bin 0 -> 6148 bytes .../Sources/Common/CategoryMenu.swift | 74 ++++++++++++++++++ Starbucks/Starbucks/Sources/Present/.DS_Store | Bin 0 -> 6148 bytes .../Order/OrderTableViewDataSource.swift | 63 --------------- .../Present/Order/OrderViewModel.swift | 51 ------------ .../CategoryTableViewCell.swift} | 11 +-- .../OrderCategoryViewController.swift} | 60 +++++--------- .../OrderTableViewDataSource.swift | 35 +++++++++ .../OrderTableViewDelegate.swift | 11 +-- .../OrderCategory/OrderViewModel.swift | 57 ++++++++++++++ .../OrderDetailViewController.swift | 16 ++++ .../OrderListViewController.swift} | 2 +- .../TabBar/StarbucksViewController.swift | 10 +-- 18 files changed, 288 insertions(+), 187 deletions(-) create mode 100644 Starbucks/Starbucks/.DS_Store create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/mockImage.png create mode 100644 Starbucks/Starbucks/Sources/.DS_Store create mode 100644 Starbucks/Starbucks/Sources/Common/CategoryMenu.swift create mode 100644 Starbucks/Starbucks/Sources/Present/.DS_Store delete mode 100644 Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift delete mode 100644 Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift rename Starbucks/Starbucks/Sources/Present/{Order/OrderTableViewCell.swift => OrderCategory/CategoryTableViewCell.swift} (90%) rename Starbucks/Starbucks/Sources/Present/{Order/OrderViewController.swift => OrderCategory/OrderCategoryViewController.swift} (66%) create mode 100644 Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift rename Starbucks/Starbucks/Sources/Present/{Order => OrderCategory}/OrderTableViewDelegate.swift (57%) create mode 100644 Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift create mode 100644 Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift rename Starbucks/Starbucks/Sources/Present/{Order/DetailOrderViewController.swift => OrderList/OrderListViewController.swift} (91%) diff --git a/Starbucks/.DS_Store b/Starbucks/.DS_Store index cbd86d1cec4b8a8033f3937dbf1c832f9d8d0ea8..ce8583c9fcc4c7b6710e1110007696fbe9ece47d 100644 GIT binary patch delta 566 zcmZoMXmOBWU|?W$DortDU;r^WfEYvza8E20o2aKKDgcrP@);O18FCm>J#+GtlX5l- za)_~PR^aGmUd+zHF|mQA9;A$&A%G#DA%!6gS!sR}$TT2!1Y*7aU;tz>Fz}$NX3zr~ zpU;rYkPWc{u5Ue>J{G9HVq|qd3m9dQ6;9@1k#^=}2xcf@NMtBtNMa}j8O?yA38<$V zSr3qnp<}WWYZHg5v89fJiJ8gdGpzRY7)lt5%YuvYa`N-ifd&ABia{9DTA=SLfDQnK zPAWqI&_VeOS;+1OYW#`j6jAJ^Okx$0LDk6wvKJn*lb^BZO%7uDgr*1_{PSSJKbeC~ zVX_PBhs{St?3pJv@CY&kjQ|1(ZXn?bihW=Ve`lV|FXB1bpNE43=qpgdVAvebGlv-f D*$|9S delta 132 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50D9R3!2Z@yh7v<&T=cR-A8w-~+ zGqO*fB_O@ISV)#}@)_X|8w)QpE@tQ85M%}_00IGSAmIu!Xk+1b=E?jro*-ixm>^bw MY-QLS&ohS^06E$hy8r+H diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index ba9ea0a..d8cc645 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -7,11 +7,17 @@ objects = { /* Begin PBXBuildFile section */ + 0039F559282A9714002F0D40 /* OrderListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039F553282A9713002F0D40 /* OrderListViewController.swift */; }; + 0039F55A282A9714002F0D40 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039F554282A9713002F0D40 /* OrderViewModel.swift */; }; + 0039F55B282A9714002F0D40 /* OrderTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039F555282A9714002F0D40 /* OrderTableViewDataSource.swift */; }; + 0039F55C282A9714002F0D40 /* CategoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039F556282A9714002F0D40 /* CategoryTableViewCell.swift */; }; + 0039F55D282A9714002F0D40 /* OrderCategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039F557282A9714002F0D40 /* OrderCategoryViewController.swift */; }; + 0039F55E282A9714002F0D40 /* OrderTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0039F558282A9714002F0D40 /* OrderTableViewDelegate.swift */; }; + 008BF423282A9BD5004A6FE5 /* OrderDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 008BF422282A9BD4004A6FE5 /* OrderDetailViewController.swift */; }; + 008BF425282AA80B004A6FE5 /* CategoryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 008BF424282AA80B004A6FE5 /* CategoryMenu.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; - E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230322829FDF900AF3E16 /* OrderViewController.swift */; }; - E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230332829FDF900AF3E16 /* OrderViewModel.swift */; }; E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230352829FDF900AF3E16 /* HomeViewModel.swift */; }; E072304A2829FDF900AF3E16 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230362829FDF900AF3E16 /* HomeViewController.swift */; }; E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230372829FDF900AF3E16 /* RootWindow.swift */; }; @@ -48,6 +54,14 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 0039F553282A9713002F0D40 /* OrderListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderListViewController.swift; sourceTree = ""; }; + 0039F554282A9713002F0D40 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; + 0039F555282A9714002F0D40 /* OrderTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDataSource.swift; sourceTree = ""; }; + 0039F556282A9714002F0D40 /* CategoryTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryTableViewCell.swift; sourceTree = ""; }; + 0039F557282A9714002F0D40 /* OrderCategoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderCategoryViewController.swift; sourceTree = ""; }; + 0039F558282A9714002F0D40 /* OrderTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDelegate.swift; sourceTree = ""; }; + 008BF422282A9BD4004A6FE5 /* OrderDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailViewController.swift; sourceTree = ""; }; + 008BF424282AA80B004A6FE5 /* CategoryMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryMenu.swift; sourceTree = ""; }; 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.debug.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.debug.xcconfig"; sourceTree = ""; }; 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -56,8 +70,6 @@ E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E07230322829FDF900AF3E16 /* OrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewController.swift; sourceTree = ""; }; - E07230332829FDF900AF3E16 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; E07230352829FDF900AF3E16 /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; E07230362829FDF900AF3E16 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; E07230372829FDF900AF3E16 /* RootWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; @@ -105,6 +117,22 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 008BF426282AB3DD004A6FE5 /* OrderList */ = { + isa = PBXGroup; + children = ( + 0039F553282A9713002F0D40 /* OrderListViewController.swift */, + ); + path = OrderList; + sourceTree = ""; + }; + 008BF427282AB3E6004A6FE5 /* OrderDetail */ = { + isa = PBXGroup; + children = ( + 008BF422282A9BD4004A6FE5 /* OrderDetailViewController.swift */, + ); + path = OrderDetail; + sourceTree = ""; + }; D660B86900EFFD9394B4E3A6 /* Pods */ = { isa = PBXGroup; children = ( @@ -180,7 +208,9 @@ E07230302829FDF900AF3E16 /* Present */ = { isa = PBXGroup; children = ( - E07230312829FDF900AF3E16 /* Order */, + 008BF427282AB3E6004A6FE5 /* OrderDetail */, + 008BF426282AB3DD004A6FE5 /* OrderList */, + E07230312829FDF900AF3E16 /* OrderCategory */, E07230342829FDF900AF3E16 /* Home */, E07230372829FDF900AF3E16 /* RootWindow.swift */, E07230382829FDF900AF3E16 /* Pay */, @@ -189,13 +219,16 @@ path = Present; sourceTree = ""; }; - E07230312829FDF900AF3E16 /* Order */ = { + E07230312829FDF900AF3E16 /* OrderCategory */ = { isa = PBXGroup; children = ( - E07230322829FDF900AF3E16 /* OrderViewController.swift */, - E07230332829FDF900AF3E16 /* OrderViewModel.swift */, + 0039F555282A9714002F0D40 /* OrderTableViewDataSource.swift */, + 0039F558282A9714002F0D40 /* OrderTableViewDelegate.swift */, + 0039F557282A9714002F0D40 /* OrderCategoryViewController.swift */, + 0039F556282A9714002F0D40 /* CategoryTableViewCell.swift */, + 0039F554282A9713002F0D40 /* OrderViewModel.swift */, ); - path = Order; + path = OrderCategory; sourceTree = ""; }; E07230342829FDF900AF3E16 /* Home */ = { @@ -292,6 +325,7 @@ isa = PBXGroup; children = ( E0723339282A724500AF3E16 /* Log.swift */, + 008BF424282AA80B004A6FE5 /* CategoryMenu.swift */, ); path = Common; sourceTree = ""; @@ -517,17 +551,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */, + 008BF423282A9BD5004A6FE5 /* OrderDetailViewController.swift in Sources */, E072333A282A724500AF3E16 /* Log.swift in Sources */, E072304C2829FDF900AF3E16 /* PayViewController.swift in Sources */, + 0039F55D282A9714002F0D40 /* OrderCategoryViewController.swift in Sources */, E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */, E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */, + 0039F55E282A9714002F0D40 /* OrderTableViewDelegate.swift in Sources */, E0723064282A1A5000AF3E16 /* Response.swift in Sources */, E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */, E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */, - E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */, + 0039F559282A9714002F0D40 /* OrderListViewController.swift in Sources */, E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */, + 008BF425282AA80B004A6FE5 /* CategoryMenu.swift in Sources */, + 0039F55B282A9714002F0D40 /* OrderTableViewDataSource.swift in Sources */, E0723060282A17B800AF3E16 /* StarbucksRepository.swift in Sources */, + 0039F55A282A9714002F0D40 /* OrderViewModel.swift in Sources */, E072304A2829FDF900AF3E16 /* HomeViewController.swift in Sources */, E072305A282A13F300AF3E16 /* StarbucksTarget.swift in Sources */, E0723337282A71B700AF3E16 /* Result+Extension.swift in Sources */, @@ -538,6 +577,7 @@ E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */, E0723067282A3C2300AF3E16 /* StarbucksEntity.swift in Sources */, E072304F2829FDF900AF3E16 /* APIError.swift in Sources */, + 0039F55C282A9714002F0D40 /* CategoryTableViewCell.swift in Sources */, E07230502829FDF900AF3E16 /* HTTPContentType.swift in Sources */, E072305D282A178700AF3E16 /* NetworkRepository.swift in Sources */, ); @@ -693,6 +733,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -722,6 +763,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Starbucks/Starbucks/.DS_Store b/Starbucks/Starbucks/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..65316e80e6698ecc43a6dac3fa5338bd6ee0288f GIT binary patch literal 6148 zcmeHK!EVz)5S>j!v!SY#14z9fS>jriKue{Hi%H9&*BZeAP_P?QEQ~jb9dd{w`P_aC zSH6Vbfj7GwAP%6O5P}(L_N`~%v$M~R*GoidFdf||>JgDfGPb&CeiA&+xfU&7*#s(k zj*Kp7N)w8yT4PJ$Z*+j~-8LPQp@QDfkM}R*+G%diliVa=@gwpWx%b5edPFzr)anfF z(U@}ROR5^0j}48cGfKfT_DMOXbd|5}H258gzH_N;u&;j4PG;rU|Nc+OIM2$e-~X&y z8|@pL9o^Ae`c?GS%%Uo;rsW`>yx^m!#^lMeA1BYUbT;hWIyOZWXGJ=(f^3w6^746B zjLd9crp2hV+Q<#)uI>(dJM;O&{iEIB;P7Cv8_bU$^wHlxTr9f!_T77r&W7*u_l3E{ z3=j(cARF5rm%uY@c<>>b6uBubFtSuF*@EJLI3NyO#{vKD&|B9b3r8Ff2mUJu`27$d z8AFe?L;LDLW48dnHp({8=MP2J1Rg_=wL^>`l#>FTRQWB2a&puI&kH@)4xOBo-+U;4 zW#xA$%3dAk4=kNj=uld5Kpa?gV8b2uc>RC!>;3<#NRGq-ap0eFK($YzlL4mW*VeVk v@md=oUn4n~S3CSBLB}q|;PO(ugS3Hqz#U-dv37_GLOukn4bq4Mf7F4mSH^+) literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json new file mode 100644 index 0000000..4c27d3e --- /dev/null +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "mockImage.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/mockImage.png b/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/mockImage.png new file mode 100644 index 0000000000000000000000000000000000000000..98b8a88db20979cbccb470c41d1ce0dac9d5ce6b GIT binary patch literal 122768 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 literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/.DS_Store b/Starbucks/Starbucks/Sources/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b2afc82c4b313951b90f6a3bd65bb3c0cddf83cd GIT binary patch literal 6148 zcmeHKPixdb6o0e3?Pgnq6%~3B0$xj9Z7mcp>2`bQL6AoDpt2^-T7zk3NwP*+7IN0_ z;8*bKC-J*@(%+kzLX%d(%c3$by!p+a_ugcFOlI2M@JLGQw&HffwnvBbUd2xlKfc1_Q^RA09>FT&w`;`y z-mvUqwHfSkXxe=1*6Hu9!!nF^U{AKOTP$;Sw0N1 z=iGWCWfGp(gYaoQDaZYr2Qn>!IGtq5A&w?6dGRbxBUujRERAyI>+1oh=k&(?jrsh+ z_THwuv%9m{bmx2b2dHoFE*3rK)}6Z#kH>G5vs8Y>3LpY!vXW~C&w-h1aPZF0(nO}b zcJ^AMV{kJCpUiy$%;-a%RqO_$&lmQ`4f5$kF*^CfJ#sM$f?8P;17?3X>r0x?PGcFc z4E#F`@cH0E89jrQMs;+cP$>YggKj0LtDk?M%N;<^V5Jc~5TRXx+EthoLzvt_Xm^Zz z#=g?1-ASmK5y#9dOok#%>LFZZPQue@OUr;|pw2*BH~W15AO1T3uRGb6Wxz7okly3kip7PhN2Di;*yI#w0FiZ`H2Fy?Xv=ozduq6cDs1QZRnuna7f FfuC7hxo7|Y literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/Common/CategoryMenu.swift b/Starbucks/Starbucks/Sources/Common/CategoryMenu.swift new file mode 100644 index 0000000..190be96 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/CategoryMenu.swift @@ -0,0 +1,74 @@ +// +// CategoryMenu.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/10. +// + +import Foundation + +protocol Menus { + var name: String { get } +} + +enum BeverageMenu: Menus, CaseIterable { + case coldBrew, espresso, frappuccino, gucci + + var name: String { + switch self { + case .coldBrew: + return "콜드브루" + case .espresso: + return "에스프레소" + case .frappuccino: + return "프라푸치노" + case .gucci: + return "구찌탕" + } + } +} + +enum FoodMenu: Menus, CaseIterable { + case bread, cake, sandwich + + var name: String { + switch self { + case .bread: + return "빵" + case .cake: + return "케이크" + case .sandwich: + return "샌드위치" + } + } +} + +enum ProductMenu: Menus, CaseIterable { + case mug, glass, tumbler + + var name: String { + switch self { + case .mug: + return "머그컵" + case .glass: + return "유리잔" + case .tumbler: + return "텀블러" + } + } +} + +enum Category: CaseIterable { + case beverage, food, product + + var name: String { + switch self { + case .beverage: + return "음료" + case .food: + return "푸드" + case .product: + return "상품" + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/.DS_Store b/Starbucks/Starbucks/Sources/Present/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..65fce4bedea25d6e69703aa6ceae2122d27fcd59 GIT binary patch literal 6148 zcmeHK&ui2`6n?Y2?Pe)L4hp>p0k5U5wiczAn6{uNr78BHvL+^KFwK-C+fbH;ob`|K z>R;mj;z_?ZGnL)c_9&wB;LZ2u{m3w1!b~0!iC~)CBXWtzfim_+7=9xhXT2mn;~4@4 zKjUdG-^RI&G3N(b`TH5*cQ>FXBk z$COb479};bNfpM7-cjAD=QS7WSLq0=#1vrcurI!Maxt}H8L$jk2CikmoOjOJwOKZ3 z8L$lej|}kn;6WLEgRMq&bYM^|0B``e64>h3ALy|I=o@S`!UGYy6sSvuX)%Q99E2`M z+&B5HMqN%qt&HcGm4)e0glRm4r_xFI8trHquna6S(AQI!@Bf3V`~T%4JF^T}2L34q z#9%+%53nV3w_e&D-?bj}4U~oRT8+O-V5qAYxqKC`LzTequ>tfAwi@Aq*dGB!gB>gb Hf0Tirwpxh{ literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift deleted file mode 100644 index c1a601e..0000000 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDataSource.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// OrderTableViewDataSource.swift -// Starbucks -// -// Created by 김상혁 on 2022/05/10. -// - -import UIKit -import RxSwift -import RxRelay - -struct Menu { - var mainLanguageName: String - var subLanguageName: String -} - -class OrderTableViewDataSource: NSObject, UITableViewDataSource { - - let mainLanguage = ["콜드브루", "블론드", "에스프레소"] - let subLanguage = ["Cold Brew", "Blonde Coffe", "Young-Jin"] - - let receiver = PublishRelay() - let sender = PublishRelay() - let disposeBag = DisposeBag() - - override init() { - super.init() - - receiver - .compactMap { [weak self] in - self?.mainLanguage[$0] - } - .bind(to: sender) - .disposed(by: disposeBag) - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let rowCount = mainLanguage.count - return rowCount - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "OrderTableViewCell", for: indexPath) - as? OrderTableViewCell else { - return UITableViewCell() - } - - return configure(cell: cell, index: indexPath.row) - } - - private func configure(cell: OrderTableViewCell, index: Int) -> UITableViewCell { - setSelectedBackgroundView(of: cell) - cell.setMenuName(text: mainLanguage[index]) - cell.setSubName(text: subLanguage[index]) - return cell - } - - private func setSelectedBackgroundView(of cell: UITableViewCell) { - let view = UIView() - view.backgroundColor = .systemBackground - cell.selectedBackgroundView = view - } -} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift deleted file mode 100644 index d07dcd4..0000000 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderViewModel.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// OrderViewModel.swift -// Starbucks -// -// Created by 김상혁 on 2022/05/09. -// - -import Foundation -import RxRelay -import RxSwift - -protocol OrderViewModelAction { - var viewDidLoad: PublishRelay { get } -} - -protocol OrderViewModelState { - var test: PublishRelay { get } -} - -protocol OrderViewModelBinding { - func action() -> OrderViewModelAction - func state() -> OrderViewModelState -} - -typealias OrderViewModelProtocol = OrderViewModelBinding - -class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelBinding { - - func action() -> OrderViewModelAction { self } - - let viewDidLoad = PublishRelay() - - func state() -> OrderViewModelState { self } - - let test = PublishRelay() - - let disposeBag = DisposeBag() - - init() { - - viewDidLoad - .map { "map test" } - .bind(to: test) - .disposed(by: disposeBag) - - viewDidLoad - .map { "gucci test" } - .bind(to: test) - .disposed(by: disposeBag) - } -} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewCell.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift similarity index 90% rename from Starbucks/Starbucks/Sources/Present/Order/OrderTableViewCell.swift rename to Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift index 722c40a..cee73ff 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewCell.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift @@ -7,9 +7,9 @@ import UIKit -class OrderTableViewCell: UITableViewCell { +class CategoryTableViewCell: UITableViewCell { - static let identifier = "OrderTableViewCell" + static var identifier: String { "\(self)" } private let menuImageView: UIImageView = { let image = UIImage(named: "mockImage.png") @@ -43,6 +43,7 @@ class OrderTableViewCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) layout() + selectionStyle = .none } @available(*, unavailable) @@ -57,7 +58,7 @@ class OrderTableViewCell: UITableViewCell { menuNameStackView.addArrangedSubview(subNameLabel) menuImageView.snp.makeConstraints { - $0.leading.equalToSuperview().offset(30) + $0.leading.equalToSuperview().offset(20) $0.centerY.equalToSuperview() $0.width.height.equalTo(80) } @@ -75,8 +76,4 @@ class OrderTableViewCell: UITableViewCell { func setSubName(text: String) { subNameLabel.text = text } - - // func setMenuName(texts: [String: String]) { - // - // } } diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift similarity index 66% rename from Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift rename to Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index f5f2c3e..19290ea 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -1,5 +1,5 @@ // -// OrderViewController.swift +// OrderCategoryViewController.swift // Starbucks // // Created by seongha shin on 2022/05/09. @@ -11,11 +11,11 @@ import RxSwift import SnapKit import UIKit -class OrderViewController: UIViewController { +class OrderCategoryViewController: UIViewController { private let tableView = UITableView() - private let tableViewDataSource = OrderTableViewDataSource() - private let tableViewDe1232131legate32132213213213 = OrderTableViewDelegate() + private var tableViewDataSource: OrderTableViewDataSource? + private let tableViewHandler = OrderTableViewDelegate() private let orderLabel: UILabel = { let label = UILabel() @@ -58,8 +58,6 @@ class OrderViewController: UIViewController { bind() attribute() layout() - -// tableViewDe1232131legate32132213213213.delegate = self } @available(*, unavailable) @@ -69,24 +67,18 @@ class OrderViewController: UIViewController { private func bind() { rx.viewDidLoad - .bind(to: viewModel.action().viewDidLoad) + .map { _ in Category.beverage } + .bind(to: viewModel.action().loadCategory) .disposed(by: disposeBag) - viewModel.state().test - .bind(onNext: { - print($0) + viewModel.state().loadedCategory + .bind(onNext: { menu in + self.updateDatasource(menu: menu) }) .disposed(by: disposeBag) - tableViewDe1232131legate32132213213213.selectedCellIndex - .bind(to: tableViewDataSource.receiver) - .disposed(by: disposeBag) - - tableViewDataSource.sender - .bind { - let detailOrderView = DetailOrderViewController(title: $0) - self.navigationController?.pushViewController(detailOrderView, animated: true) - } + tableViewHandler.selectedCellIndex + .bind(to: viewModel.action().loadCategoryList) .disposed(by: disposeBag) } @@ -119,7 +111,7 @@ class OrderViewController: UIViewController { categoryStackView.snp.makeConstraints { $0.top.equalToSuperview().offset(2) - $0.leading.equalToSuperview().offset(20) + $0.leading.equalToSuperview().offset(30) $0.height.equalTo(45) } @@ -133,29 +125,17 @@ class OrderViewController: UIViewController { private func configureTableView() { tableView.rowHeight = 100 tableView.separatorStyle = .none - tableView.register(OrderTableViewCell.self, forCellReuseIdentifier: OrderTableViewCell.identifier) - tableView.dataSource = tableViewDataSource - tableView.delegate = tableViewDe1232131legate32132213213213 + tableView.register(CategoryTableViewCell.self, forCellReuseIdentifier: CategoryTableViewCell.identifier) + tableView.delegate = tableViewHandler } } -extension OrderViewController: CellSelectionDetectable { - func didSelectCell(indexPath: IndexPath) { - print(tableViewDataSource.mainLanguage[indexPath.row]) - } -} - -enum Category: CaseIterable { - case beverage, food, product - - var name: String { - switch self { - case .beverage: - return "음료" - case .food: - return "푸드" - case .product: - return "상품" +extension OrderCategoryViewController { + private func updateDatasource(menu: [Menus]) { + self.tableViewDataSource = OrderTableViewDataSource(menus: menu) + DispatchQueue.main.async { + self.tableView.dataSource = self.tableViewDataSource + self.tableView.reloadData() } } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift new file mode 100644 index 0000000..96f2023 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift @@ -0,0 +1,35 @@ +// +// OrderTableViewDataSource.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import UIKit +import RxRelay +import RxSwift + +class OrderTableViewDataSource: NSObject, UITableViewDataSource { + + private let menus: [Menus]! + + init(menus: [Menus]) { + self.menus = menus + super.init() + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + print(menus.count) + return menus.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: CategoryTableViewCell.identifier, for: indexPath) + as? CategoryTableViewCell else { + return UITableViewCell() + } + cell.setMenuName(text: menus[indexPath.row].name) + cell.setSubName(text: menus[indexPath.row].name) + return cell + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift similarity index 57% rename from Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift rename to Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift index fda8f84..bf59edc 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/OrderTableViewDelegate.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift @@ -6,8 +6,8 @@ // import UIKit -import RxSwift import RxRelay +import RxSwift protocol CellSelectionDetectable: AnyObject { func didSelectCell(indexPath: IndexPath) @@ -15,20 +15,13 @@ protocol CellSelectionDetectable: AnyObject { class OrderTableViewDelegate: NSObject, UITableViewDelegate { -// weak var delegate: CellSelectionDetectable? - let selectedCellIndex = PublishSubject() func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { selectedCellIndex .onNext(indexPath.row) - //delegate가 indexPath를 발행해줌 - //dataSource가 indexPath를 받아야함 - //dataSource가 VC에게 indexPath에 있는 cell의 정보를 발행해줌 - //VC가 그 정보를 받아서 NavigationVC를 Push -// print(indexPath.row) -// delegate?.didSelectCell(indexPath: indexPath) + print(indexPath.row) } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift new file mode 100644 index 0000000..9c2c76c --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -0,0 +1,57 @@ +// +// OrderViewModel.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/09. +// + +import Foundation +import RxRelay +import RxSwift + +protocol OrderViewModelAction { + var loadCategory: BehaviorRelay { get } + var loadCategoryList: PublishRelay { get } +} + +protocol OrderViewModelState { + var loadedCategory: PublishRelay<[Menus]> { get } +} + +protocol OrderViewModelBinding { + func action() -> OrderViewModelAction + func state() -> OrderViewModelState +} + +typealias OrderViewModelProtocol = OrderViewModelBinding + +class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelBinding { + + func action() -> OrderViewModelAction { self } + + let loadCategory = BehaviorRelay(value: .beverage) + let loadCategoryList = PublishRelay() + + func state() -> OrderViewModelState { self } + let loadedCategory = PublishRelay<[Menus]>() + + + private let disposeBag = DisposeBag() + private let categoryMenu: [Category : [Menus]] = [.beverage : BeverageMenu.allCases, + .food : FoodMenu.allCases, + .product : ProductMenu.allCases] + + init() { + loadCategory + .compactMap { self.categoryMenu[$0] } + .bind(to: loadedCategory) + .disposed(by: disposeBag) + + Observable + .combineLatest(loadCategory, loadCategoryList) + .bind(onNext: { category, menuIndex in + print(category, menuIndex) + }) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift new file mode 100644 index 0000000..36588dd --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift @@ -0,0 +1,16 @@ +// +// OrderDetailViewController.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/10. +// + +import UIKit + +class OrderDetailViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Order/DetailOrderViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift similarity index 91% rename from Starbucks/Starbucks/Sources/Present/Order/DetailOrderViewController.swift rename to Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index 9cb4850..00f678e 100644 --- a/Starbucks/Starbucks/Sources/Present/Order/DetailOrderViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -7,7 +7,7 @@ import UIKit -class DetailOrderViewController: UIViewController { +class OrderListViewController: UIViewController { init(title: String) { super.init(nibName: nil, bundle: nil) diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index 443130b..a7b72d2 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -25,13 +25,13 @@ class StarbucksViewController: UITabBarController { payViewController.tabBarItem.title = "Pay" payViewController.tabBarItem.image = UIImage(named: "ic_temp") - let rootVC = OrderViewController(viewModel: OrderViewModel()) - let orderViewController = UINavigationController(rootViewController: rootVC) - orderViewController.tabBarItem.title = "Order" - orderViewController.tabBarItem.image = UIImage(named: "ic_temp") + let orderViewController = OrderCategoryViewController(viewModel: OrderViewModel()) + let orderNavigationController = UINavigationController(rootViewController: orderViewController) + orderNavigationController.tabBarItem.title = "Order" + orderNavigationController.tabBarItem.image = UIImage(named: "ic_temp") viewControllers = [ - homeViewController, payViewController, orderViewController + homeViewController, payViewController, orderNavigationController ] } } From 7b1ee6226ee4d9e091b27cdf51147c98d27b4471 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 11:23:28 +0900 Subject: [PATCH 17/57] =?UTF-8?q?[STAR-10]=20feature:=20=ED=99=88=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20?= =?UTF-8?q?=ED=86=B5=EC=8B=A0=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 282 +++++++++--------- .../Starbucks/Sources/API/Base/Provider.swift | 4 +- .../Sources/Present/Home/HomeViewModel.swift | 38 ++- 3 files changed, 173 insertions(+), 151 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index ba9ea0a..fd1c2f5 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -10,30 +10,34 @@ 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; - E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230322829FDF900AF3E16 /* OrderViewController.swift */; }; - E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230332829FDF900AF3E16 /* OrderViewModel.swift */; }; - E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230352829FDF900AF3E16 /* HomeViewModel.swift */; }; - E072304A2829FDF900AF3E16 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230362829FDF900AF3E16 /* HomeViewController.swift */; }; - E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230372829FDF900AF3E16 /* RootWindow.swift */; }; - E072304C2829FDF900AF3E16 /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230392829FDF900AF3E16 /* PayViewController.swift */; }; - E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072303B2829FDF900AF3E16 /* StarbucksViewController.swift */; }; - E072304E2829FDF900AF3E16 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072303D2829FDF900AF3E16 /* UIColor+Extension.swift */; }; - E072304F2829FDF900AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230402829FDF900AF3E16 /* APIError.swift */; }; - E07230502829FDF900AF3E16 /* HTTPContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230412829FDF900AF3E16 /* HTTPContentType.swift */; }; - E07230512829FDF900AF3E16 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230422829FDF900AF3E16 /* Provider.swift */; }; - E07230522829FDF900AF3E16 /* BaseTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230432829FDF900AF3E16 /* BaseTarget.swift */; }; - E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230442829FDF900AF3E16 /* HTTPMethod.swift */; }; - E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230452829FDF900AF3E16 /* AppDelegate.swift */; }; - E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07230462829FDF900AF3E16 /* SceneDelegate.swift */; }; E0723058282A13C900AF3E16 /* StarbuckTargetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */; }; - E072305A282A13F300AF3E16 /* StarbucksTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723059282A13F300AF3E16 /* StarbucksTarget.swift */; }; - E072305D282A178700AF3E16 /* NetworkRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072305C282A178700AF3E16 /* NetworkRepository.swift */; }; - E0723060282A17B800AF3E16 /* StarbucksRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072305F282A17B800AF3E16 /* StarbucksRepository.swift */; }; - E0723062282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723061282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift */; }; - E0723064282A1A5000AF3E16 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723063282A1A5000AF3E16 /* Response.swift */; }; - E0723067282A3C2300AF3E16 /* StarbucksEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723066282A3C2300AF3E16 /* StarbucksEntity.swift */; }; - E0723337282A71B700AF3E16 /* Result+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723336282A71B700AF3E16 /* Result+Extension.swift */; }; - E072333A282A724500AF3E16 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723339282A724500AF3E16 /* Log.swift */; }; + E0723363282A7B3F00AF3E16 /* OrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072333E282A7B3F00AF3E16 /* OrderViewController.swift */; }; + E0723364282A7B3F00AF3E16 /* OrderTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072333F282A7B3F00AF3E16 /* OrderTableViewDelegate.swift */; }; + E0723365282A7B3F00AF3E16 /* OrderTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723340282A7B3F00AF3E16 /* OrderTableViewDataSource.swift */; }; + E0723366282A7B3F00AF3E16 /* DetailOrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723341282A7B3F00AF3E16 /* DetailOrderViewController.swift */; }; + E0723367282A7B3F00AF3E16 /* OrderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723342282A7B3F00AF3E16 /* OrderTableViewCell.swift */; }; + E0723368282A7B3F00AF3E16 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723343282A7B3F00AF3E16 /* OrderViewModel.swift */; }; + E0723369282A7B3F00AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723345282A7B3F00AF3E16 /* HomeViewModel.swift */; }; + E072336A282A7B3F00AF3E16 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723346282A7B3F00AF3E16 /* HomeViewController.swift */; }; + E072336B282A7B3F00AF3E16 /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723347282A7B3F00AF3E16 /* RootWindow.swift */; }; + E072336C282A7B3F00AF3E16 /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723349282A7B3F00AF3E16 /* PayViewController.swift */; }; + E072336D282A7B3F00AF3E16 /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072334B282A7B3F00AF3E16 /* StarbucksViewController.swift */; }; + E072336E282A7B3F00AF3E16 /* StarbucksRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072334E282A7B3F00AF3E16 /* StarbucksRepository.swift */; }; + E072336F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072334F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift */; }; + E0723370282A7B3F00AF3E16 /* NetworkRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723350282A7B3F00AF3E16 /* NetworkRepository.swift */; }; + E0723371282A7B3F00AF3E16 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723352282A7B3F00AF3E16 /* UIColor+Extension.swift */; }; + E0723372282A7B3F00AF3E16 /* Result+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723353282A7B3F00AF3E16 /* Result+Extension.swift */; }; + E0723373282A7B3F00AF3E16 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723355282A7B3F00AF3E16 /* Log.swift */; }; + E0723374282A7B3F00AF3E16 /* StarbucksEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723357282A7B3F00AF3E16 /* StarbucksEntity.swift */; }; + E0723375282A7B3F00AF3E16 /* StarbucksTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723359282A7B3F00AF3E16 /* StarbucksTarget.swift */; }; + E0723376282A7B3F00AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335B282A7B3F00AF3E16 /* APIError.swift */; }; + E0723377282A7B3F00AF3E16 /* HTTPContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335C282A7B3F00AF3E16 /* HTTPContentType.swift */; }; + E0723378282A7B3F00AF3E16 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335D282A7B3F00AF3E16 /* Provider.swift */; }; + E0723379282A7B3F00AF3E16 /* BaseTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335E282A7B3F00AF3E16 /* BaseTarget.swift */; }; + E072337A282A7B3F00AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335F282A7B3F00AF3E16 /* HTTPMethod.swift */; }; + E072337B282A7B3F00AF3E16 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723360282A7B3F00AF3E16 /* Response.swift */; }; + E072337C282A7B3F00AF3E16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723361282A7B3F00AF3E16 /* AppDelegate.swift */; }; + E072337D282A7B3F00AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723362282A7B3F00AF3E16 /* SceneDelegate.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -56,30 +60,34 @@ E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E07230322829FDF900AF3E16 /* OrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewController.swift; sourceTree = ""; }; - E07230332829FDF900AF3E16 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; - E07230352829FDF900AF3E16 /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; - E07230362829FDF900AF3E16 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; - E07230372829FDF900AF3E16 /* RootWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; - E07230392829FDF900AF3E16 /* PayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; - E072303B2829FDF900AF3E16 /* StarbucksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; - E072303D2829FDF900AF3E16 /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; - E07230402829FDF900AF3E16 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; - E07230412829FDF900AF3E16 /* HTTPContentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPContentType.swift; sourceTree = ""; }; - E07230422829FDF900AF3E16 /* Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; - E07230432829FDF900AF3E16 /* BaseTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTarget.swift; sourceTree = ""; }; - E07230442829FDF900AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; - E07230452829FDF900AF3E16 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - E07230462829FDF900AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbuckTargetTest.swift; sourceTree = ""; }; - E0723059282A13F300AF3E16 /* StarbucksTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTarget.swift; sourceTree = ""; }; - E072305C282A178700AF3E16 /* NetworkRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRepository.swift; sourceTree = ""; }; - E072305F282A17B800AF3E16 /* StarbucksRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksRepository.swift; sourceTree = ""; }; - E0723061282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksRepositoryImpl.swift; sourceTree = ""; }; - E0723063282A1A5000AF3E16 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; - E0723066282A3C2300AF3E16 /* StarbucksEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksEntity.swift; sourceTree = ""; }; - E0723336282A71B700AF3E16 /* Result+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extension.swift"; sourceTree = ""; }; - E0723339282A724500AF3E16 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + E072333E282A7B3F00AF3E16 /* OrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewController.swift; sourceTree = ""; }; + E072333F282A7B3F00AF3E16 /* OrderTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDelegate.swift; sourceTree = ""; }; + E0723340282A7B3F00AF3E16 /* OrderTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDataSource.swift; sourceTree = ""; }; + E0723341282A7B3F00AF3E16 /* DetailOrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailOrderViewController.swift; sourceTree = ""; }; + E0723342282A7B3F00AF3E16 /* OrderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewCell.swift; sourceTree = ""; }; + E0723343282A7B3F00AF3E16 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; + E0723345282A7B3F00AF3E16 /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + E0723346282A7B3F00AF3E16 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + E0723347282A7B3F00AF3E16 /* RootWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; + E0723349282A7B3F00AF3E16 /* PayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; + E072334B282A7B3F00AF3E16 /* StarbucksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; + E072334E282A7B3F00AF3E16 /* StarbucksRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksRepository.swift; sourceTree = ""; }; + E072334F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksRepositoryImpl.swift; sourceTree = ""; }; + E0723350282A7B3F00AF3E16 /* NetworkRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkRepository.swift; sourceTree = ""; }; + E0723352282A7B3F00AF3E16 /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + E0723353282A7B3F00AF3E16 /* Result+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Result+Extension.swift"; sourceTree = ""; }; + E0723355282A7B3F00AF3E16 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + E0723357282A7B3F00AF3E16 /* StarbucksEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksEntity.swift; sourceTree = ""; }; + E0723359282A7B3F00AF3E16 /* StarbucksTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksTarget.swift; sourceTree = ""; }; + E072335B282A7B3F00AF3E16 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; + E072335C282A7B3F00AF3E16 /* HTTPContentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPContentType.swift; sourceTree = ""; }; + E072335D282A7B3F00AF3E16 /* Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; + E072335E282A7B3F00AF3E16 /* BaseTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTarget.swift; sourceTree = ""; }; + E072335F282A7B3F00AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + E0723360282A7B3F00AF3E16 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; + E0723361282A7B3F00AF3E16 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E0723362282A7B3F00AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -139,7 +147,7 @@ E01B527828289D17009918AE /* Starbucks */ = { isa = PBXGroup; children = ( - E072302F2829FDF900AF3E16 /* Sources */, + E072333B282A7B3F00AF3E16 /* Sources */, E01B528F2828A0C3009918AE /* Resource */, E01B52902828A0CE009918AE /* Configuration */, ); @@ -162,138 +170,142 @@ path = Configuration; sourceTree = ""; }; - E072302F2829FDF900AF3E16 /* Sources */ = { + E0723056282A13AB00AF3E16 /* API */ = { + isa = PBXGroup; + children = ( + E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */, + ); + path = API; + sourceTree = ""; + }; + E072333B282A7B3F00AF3E16 /* Sources */ = { isa = PBXGroup; children = ( - E0723065282A3C1700AF3E16 /* Model */, - E072305B282A177800AF3E16 /* Repository */, - E072303E2829FDF900AF3E16 /* API */, - E07230302829FDF900AF3E16 /* Present */, - E0723338282A723F00AF3E16 /* Common */, - E072303C2829FDF900AF3E16 /* Extension */, - E07230452829FDF900AF3E16 /* AppDelegate.swift */, - E07230462829FDF900AF3E16 /* SceneDelegate.swift */, + E0723356282A7B3F00AF3E16 /* Model */, + E072334C282A7B3F00AF3E16 /* Repository */, + E0723358282A7B3F00AF3E16 /* API */, + E072333C282A7B3F00AF3E16 /* Present */, + E0723351282A7B3F00AF3E16 /* Extension */, + E0723354282A7B3F00AF3E16 /* Common */, + E0723361282A7B3F00AF3E16 /* AppDelegate.swift */, + E0723362282A7B3F00AF3E16 /* SceneDelegate.swift */, ); path = Sources; sourceTree = ""; }; - E07230302829FDF900AF3E16 /* Present */ = { + E072333C282A7B3F00AF3E16 /* Present */ = { isa = PBXGroup; children = ( - E07230312829FDF900AF3E16 /* Order */, - E07230342829FDF900AF3E16 /* Home */, - E07230372829FDF900AF3E16 /* RootWindow.swift */, - E07230382829FDF900AF3E16 /* Pay */, - E072303A2829FDF900AF3E16 /* TabBar */, + E072333D282A7B3F00AF3E16 /* Order */, + E0723344282A7B3F00AF3E16 /* Home */, + E0723347282A7B3F00AF3E16 /* RootWindow.swift */, + E0723348282A7B3F00AF3E16 /* Pay */, + E072334A282A7B3F00AF3E16 /* TabBar */, ); path = Present; sourceTree = ""; }; - E07230312829FDF900AF3E16 /* Order */ = { + E072333D282A7B3F00AF3E16 /* Order */ = { isa = PBXGroup; children = ( - E07230322829FDF900AF3E16 /* OrderViewController.swift */, - E07230332829FDF900AF3E16 /* OrderViewModel.swift */, + E072333E282A7B3F00AF3E16 /* OrderViewController.swift */, + E072333F282A7B3F00AF3E16 /* OrderTableViewDelegate.swift */, + E0723340282A7B3F00AF3E16 /* OrderTableViewDataSource.swift */, + E0723341282A7B3F00AF3E16 /* DetailOrderViewController.swift */, + E0723342282A7B3F00AF3E16 /* OrderTableViewCell.swift */, + E0723343282A7B3F00AF3E16 /* OrderViewModel.swift */, ); path = Order; sourceTree = ""; }; - E07230342829FDF900AF3E16 /* Home */ = { + E0723344282A7B3F00AF3E16 /* Home */ = { isa = PBXGroup; children = ( - E07230352829FDF900AF3E16 /* HomeViewModel.swift */, - E07230362829FDF900AF3E16 /* HomeViewController.swift */, + E0723345282A7B3F00AF3E16 /* HomeViewModel.swift */, + E0723346282A7B3F00AF3E16 /* HomeViewController.swift */, ); path = Home; sourceTree = ""; }; - E07230382829FDF900AF3E16 /* Pay */ = { + E0723348282A7B3F00AF3E16 /* Pay */ = { isa = PBXGroup; children = ( - E07230392829FDF900AF3E16 /* PayViewController.swift */, + E0723349282A7B3F00AF3E16 /* PayViewController.swift */, ); path = Pay; sourceTree = ""; }; - E072303A2829FDF900AF3E16 /* TabBar */ = { + E072334A282A7B3F00AF3E16 /* TabBar */ = { isa = PBXGroup; children = ( - E072303B2829FDF900AF3E16 /* StarbucksViewController.swift */, + E072334B282A7B3F00AF3E16 /* StarbucksViewController.swift */, ); path = TabBar; sourceTree = ""; }; - E072303C2829FDF900AF3E16 /* Extension */ = { + E072334C282A7B3F00AF3E16 /* Repository */ = { isa = PBXGroup; children = ( - E072303D2829FDF900AF3E16 /* UIColor+Extension.swift */, - E0723336282A71B700AF3E16 /* Result+Extension.swift */, + E072334D282A7B3F00AF3E16 /* Starbucks */, + E0723350282A7B3F00AF3E16 /* NetworkRepository.swift */, ); - path = Extension; - sourceTree = ""; - }; - E072303E2829FDF900AF3E16 /* API */ = { - isa = PBXGroup; - children = ( - E072303F2829FDF900AF3E16 /* Base */, - E0723059282A13F300AF3E16 /* StarbucksTarget.swift */, - ); - path = API; + path = Repository; sourceTree = ""; }; - E072303F2829FDF900AF3E16 /* Base */ = { + E072334D282A7B3F00AF3E16 /* Starbucks */ = { isa = PBXGroup; children = ( - E07230422829FDF900AF3E16 /* Provider.swift */, - E07230432829FDF900AF3E16 /* BaseTarget.swift */, - E07230442829FDF900AF3E16 /* HTTPMethod.swift */, - E07230412829FDF900AF3E16 /* HTTPContentType.swift */, - E07230402829FDF900AF3E16 /* APIError.swift */, - E0723063282A1A5000AF3E16 /* Response.swift */, + E072334E282A7B3F00AF3E16 /* StarbucksRepository.swift */, + E072334F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift */, ); - path = Base; + path = Starbucks; sourceTree = ""; }; - E0723056282A13AB00AF3E16 /* API */ = { + E0723351282A7B3F00AF3E16 /* Extension */ = { isa = PBXGroup; children = ( - E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */, + E0723352282A7B3F00AF3E16 /* UIColor+Extension.swift */, + E0723353282A7B3F00AF3E16 /* Result+Extension.swift */, ); - path = API; + path = Extension; sourceTree = ""; }; - E072305B282A177800AF3E16 /* Repository */ = { + E0723354282A7B3F00AF3E16 /* Common */ = { isa = PBXGroup; children = ( - E072305E282A17A700AF3E16 /* Starbucks */, - E072305C282A178700AF3E16 /* NetworkRepository.swift */, + E0723355282A7B3F00AF3E16 /* Log.swift */, ); - path = Repository; + path = Common; sourceTree = ""; }; - E072305E282A17A700AF3E16 /* Starbucks */ = { + E0723356282A7B3F00AF3E16 /* Model */ = { isa = PBXGroup; children = ( - E072305F282A17B800AF3E16 /* StarbucksRepository.swift */, - E0723061282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift */, + E0723357282A7B3F00AF3E16 /* StarbucksEntity.swift */, ); - path = Starbucks; + path = Model; sourceTree = ""; }; - E0723065282A3C1700AF3E16 /* Model */ = { + E0723358282A7B3F00AF3E16 /* API */ = { isa = PBXGroup; children = ( - E0723066282A3C2300AF3E16 /* StarbucksEntity.swift */, + E0723359282A7B3F00AF3E16 /* StarbucksTarget.swift */, + E072335A282A7B3F00AF3E16 /* Base */, ); - path = Model; + path = API; sourceTree = ""; }; - E0723338282A723F00AF3E16 /* Common */ = { + E072335A282A7B3F00AF3E16 /* Base */ = { isa = PBXGroup; children = ( - E0723339282A724500AF3E16 /* Log.swift */, + E072335B282A7B3F00AF3E16 /* APIError.swift */, + E072335C282A7B3F00AF3E16 /* HTTPContentType.swift */, + E072335D282A7B3F00AF3E16 /* Provider.swift */, + E072335E282A7B3F00AF3E16 /* BaseTarget.swift */, + E072335F282A7B3F00AF3E16 /* HTTPMethod.swift */, + E0723360282A7B3F00AF3E16 /* Response.swift */, ); - path = Common; + path = Base; sourceTree = ""; }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { @@ -517,29 +529,33 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E07230472829FDF900AF3E16 /* OrderViewController.swift in Sources */, - E072333A282A724500AF3E16 /* Log.swift in Sources */, - E072304C2829FDF900AF3E16 /* PayViewController.swift in Sources */, - E07230492829FDF900AF3E16 /* HomeViewModel.swift in Sources */, - E072304D2829FDF900AF3E16 /* StarbucksViewController.swift in Sources */, - E0723064282A1A5000AF3E16 /* Response.swift in Sources */, - E07230542829FDF900AF3E16 /* AppDelegate.swift in Sources */, - E07230552829FDF900AF3E16 /* SceneDelegate.swift in Sources */, - E07230482829FDF900AF3E16 /* OrderViewModel.swift in Sources */, - E072304B2829FDF900AF3E16 /* RootWindow.swift in Sources */, - E0723060282A17B800AF3E16 /* StarbucksRepository.swift in Sources */, - E072304A2829FDF900AF3E16 /* HomeViewController.swift in Sources */, - E072305A282A13F300AF3E16 /* StarbucksTarget.swift in Sources */, - E0723337282A71B700AF3E16 /* Result+Extension.swift in Sources */, - E07230512829FDF900AF3E16 /* Provider.swift in Sources */, - E072304E2829FDF900AF3E16 /* UIColor+Extension.swift in Sources */, - E0723062282A17BF00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, - E07230522829FDF900AF3E16 /* BaseTarget.swift in Sources */, - E07230532829FDF900AF3E16 /* HTTPMethod.swift in Sources */, - E0723067282A3C2300AF3E16 /* StarbucksEntity.swift in Sources */, - E072304F2829FDF900AF3E16 /* APIError.swift in Sources */, - E07230502829FDF900AF3E16 /* HTTPContentType.swift in Sources */, - E072305D282A178700AF3E16 /* NetworkRepository.swift in Sources */, + E0723370282A7B3F00AF3E16 /* NetworkRepository.swift in Sources */, + E0723366282A7B3F00AF3E16 /* DetailOrderViewController.swift in Sources */, + E072336C282A7B3F00AF3E16 /* PayViewController.swift in Sources */, + E072336E282A7B3F00AF3E16 /* StarbucksRepository.swift in Sources */, + E072336D282A7B3F00AF3E16 /* StarbucksViewController.swift in Sources */, + E0723367282A7B3F00AF3E16 /* OrderTableViewCell.swift in Sources */, + E0723369282A7B3F00AF3E16 /* HomeViewModel.swift in Sources */, + E072336A282A7B3F00AF3E16 /* HomeViewController.swift in Sources */, + E072337C282A7B3F00AF3E16 /* AppDelegate.swift in Sources */, + E0723373282A7B3F00AF3E16 /* Log.swift in Sources */, + E0723372282A7B3F00AF3E16 /* Result+Extension.swift in Sources */, + E072337D282A7B3F00AF3E16 /* SceneDelegate.swift in Sources */, + E0723363282A7B3F00AF3E16 /* OrderViewController.swift in Sources */, + E0723371282A7B3F00AF3E16 /* UIColor+Extension.swift in Sources */, + E0723368282A7B3F00AF3E16 /* OrderViewModel.swift in Sources */, + E0723376282A7B3F00AF3E16 /* APIError.swift in Sources */, + E0723379282A7B3F00AF3E16 /* BaseTarget.swift in Sources */, + E0723374282A7B3F00AF3E16 /* StarbucksEntity.swift in Sources */, + E072336B282A7B3F00AF3E16 /* RootWindow.swift in Sources */, + E0723377282A7B3F00AF3E16 /* HTTPContentType.swift in Sources */, + E0723364282A7B3F00AF3E16 /* OrderTableViewDelegate.swift in Sources */, + E0723378282A7B3F00AF3E16 /* Provider.swift in Sources */, + E072337A282A7B3F00AF3E16 /* HTTPMethod.swift in Sources */, + E072336F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, + E0723365282A7B3F00AF3E16 /* OrderTableViewDataSource.swift in Sources */, + E0723375282A7B3F00AF3E16 /* StarbucksTarget.swift in Sources */, + E072337B282A7B3F00AF3E16 /* Response.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Starbucks/Starbucks/Sources/API/Base/Provider.swift b/Starbucks/Starbucks/Sources/API/Base/Provider.swift index 4d22382..31ca805 100644 --- a/Starbucks/Starbucks/Sources/API/Base/Provider.swift +++ b/Starbucks/Starbucks/Sources/API/Base/Provider.swift @@ -54,7 +54,7 @@ class Provider { let dataRequest: DataRequest = AF.request(request) dataRequest - .response { dataResponse in + .response(queue: nil) { dataResponse in switch ( dataResponse.response, dataResponse.data, dataResponse.error) { case let (.some(urlResponse), data, .none): let response = Response(statusCode: urlResponse.statusCode, data: data ?? Data(), request: request, response: urlResponse) @@ -75,7 +75,7 @@ class Provider { } } - return Disposables.create { AF.session.invalidateAndCancel() } + return Disposables.create { } } } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 8e65363..706a3b6 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -37,32 +37,38 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta private let disposeBag = DisposeBag() init() { - -// let requestHome = action().loadHome -// .flatMapLatest { [weak self] _ in -// self?.starbucksRepository.requestHome() ?? .never() -// } -// .share() -// -// requestHome -// .compactMap { $0.value } -// .bind(onNext: { -// print($0) -// }) -// .disposed(by: disposeBag) + let requestHome = action().loadHome + .withUnretained(self) + .flatMapLatest { model, _ in + model.starbucksRepository.requestHome() + } + .share() + + requestHome + .compactMap { $0.value } + .bind(onNext: { + }) + .disposed(by: disposeBag) let requestEvent = action().loadEvent - .flatMapLatest { [weak self] _ in - self?.starbucksRepository.requestEvent() ?? .never() + .withUnretained(self) + .flatMapLatest { model, _ in + model.starbucksRepository.requestEvent() } .share() requestEvent .compactMap { $0.value } .bind(onNext: { - print($0) }) .disposed(by: disposeBag) + Observable + .merge( + requestHome.compactMap { $0.error }, + requestEvent.compactMap { $0.error }) + .bind(onNext: { + }) + .disposed(by: disposeBag) } } From ae1eebfca9c3ed955087e495e865c885981f4c1e Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Wed, 11 May 2022 14:10:28 +0900 Subject: [PATCH 18/57] =?UTF-8?q?[STAR-7]=20refactor:=20=EA=B0=95=EC=A0=9C?= =?UTF-8?q?=20=EC=96=B8=EB=9E=A9=ED=95=91,=20=ED=94=84=EB=A6=B0=ED=8A=B8?= =?UTF-8?q?=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift | 1 - .../Present/OrderCategory/OrderTableViewDataSource.swift | 3 +-- .../Sources/Present/OrderCategory/OrderTableViewDelegate.swift | 1 - .../Sources/Present/OrderCategory/OrderViewModel.swift | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 8e65363..1905b6b 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -63,6 +63,5 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta print($0) }) .disposed(by: disposeBag) - } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift index 96f2023..c41276d 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift @@ -11,7 +11,7 @@ import RxSwift class OrderTableViewDataSource: NSObject, UITableViewDataSource { - private let menus: [Menus]! + private let menus: [Menus] init(menus: [Menus]) { self.menus = menus @@ -19,7 +19,6 @@ class OrderTableViewDataSource: NSObject, UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - print(menus.count) return menus.count } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift index bf59edc..bab1c0f 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift @@ -22,6 +22,5 @@ class OrderTableViewDelegate: NSObject, UITableViewDelegate { selectedCellIndex .onNext(indexPath.row) - print(indexPath.row) } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 9c2c76c..eadb5dc 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -50,7 +50,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB Observable .combineLatest(loadCategory, loadCategoryList) .bind(onNext: { category, menuIndex in - print(category, menuIndex) + // TODO: - List Category 뷰로 연결하는 로직 }) .disposed(by: disposeBag) } From b99d7d0f8eee2628e93efea3f1999ccbef456b79 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 14:12:59 +0900 Subject: [PATCH 19/57] =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks/Sources/API/Base/Provider.swift | 2 +- Starbucks/Starbucks/Sources/API/StarbucksTarget.swift | 4 ++-- .../Starbucks/Sources/Present/Pay/PayViewController.swift | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Starbucks/Starbucks/Sources/API/Base/Provider.swift b/Starbucks/Starbucks/Sources/API/Base/Provider.swift index 31ca805..b6c6505 100644 --- a/Starbucks/Starbucks/Sources/API/Base/Provider.swift +++ b/Starbucks/Starbucks/Sources/API/Base/Provider.swift @@ -54,7 +54,7 @@ class Provider { let dataRequest: DataRequest = AF.request(request) dataRequest - .response(queue: nil) { dataResponse in + .response { dataResponse in switch ( dataResponse.response, dataResponse.data, dataResponse.error) { case let (.some(urlResponse), data, .none): let response = Response(statusCode: urlResponse.statusCode, data: data ?? Data(), request: request, response: urlResponse) diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift index 9c142c0..f2ec689 100644 --- a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -32,12 +32,12 @@ extension StarbucksTarget { } } - var parameter: [String : Any]? { + var parameter: [String: Any]? { switch self { case .requestHome: return nil case .requestEvent: - return ["MENU_CD":"all"] + return ["MENU_CD": "all"] } } diff --git a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift index 6a74181..d8fc32c 100644 --- a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift @@ -8,5 +8,4 @@ import UIKit class PayViewController: UIViewController { - } From 552601c131a66684cf4e963df3a59c23b1f0a82f Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 14:15:10 +0900 Subject: [PATCH 20/57] =?UTF-8?q?=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 302 ++++++++++-------- 1 file changed, 163 insertions(+), 139 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index fd1c2f5..f72662f 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -11,33 +11,35 @@ 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; E0723058282A13C900AF3E16 /* StarbuckTargetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */; }; - E0723363282A7B3F00AF3E16 /* OrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072333E282A7B3F00AF3E16 /* OrderViewController.swift */; }; - E0723364282A7B3F00AF3E16 /* OrderTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072333F282A7B3F00AF3E16 /* OrderTableViewDelegate.swift */; }; - E0723365282A7B3F00AF3E16 /* OrderTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723340282A7B3F00AF3E16 /* OrderTableViewDataSource.swift */; }; - E0723366282A7B3F00AF3E16 /* DetailOrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723341282A7B3F00AF3E16 /* DetailOrderViewController.swift */; }; - E0723367282A7B3F00AF3E16 /* OrderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723342282A7B3F00AF3E16 /* OrderTableViewCell.swift */; }; - E0723368282A7B3F00AF3E16 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723343282A7B3F00AF3E16 /* OrderViewModel.swift */; }; - E0723369282A7B3F00AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723345282A7B3F00AF3E16 /* HomeViewModel.swift */; }; - E072336A282A7B3F00AF3E16 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723346282A7B3F00AF3E16 /* HomeViewController.swift */; }; - E072336B282A7B3F00AF3E16 /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723347282A7B3F00AF3E16 /* RootWindow.swift */; }; - E072336C282A7B3F00AF3E16 /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723349282A7B3F00AF3E16 /* PayViewController.swift */; }; - E072336D282A7B3F00AF3E16 /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072334B282A7B3F00AF3E16 /* StarbucksViewController.swift */; }; - E072336E282A7B3F00AF3E16 /* StarbucksRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072334E282A7B3F00AF3E16 /* StarbucksRepository.swift */; }; - E072336F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072334F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift */; }; - E0723370282A7B3F00AF3E16 /* NetworkRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723350282A7B3F00AF3E16 /* NetworkRepository.swift */; }; - E0723371282A7B3F00AF3E16 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723352282A7B3F00AF3E16 /* UIColor+Extension.swift */; }; - E0723372282A7B3F00AF3E16 /* Result+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723353282A7B3F00AF3E16 /* Result+Extension.swift */; }; - E0723373282A7B3F00AF3E16 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723355282A7B3F00AF3E16 /* Log.swift */; }; - E0723374282A7B3F00AF3E16 /* StarbucksEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723357282A7B3F00AF3E16 /* StarbucksEntity.swift */; }; - E0723375282A7B3F00AF3E16 /* StarbucksTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723359282A7B3F00AF3E16 /* StarbucksTarget.swift */; }; - E0723376282A7B3F00AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335B282A7B3F00AF3E16 /* APIError.swift */; }; - E0723377282A7B3F00AF3E16 /* HTTPContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335C282A7B3F00AF3E16 /* HTTPContentType.swift */; }; - E0723378282A7B3F00AF3E16 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335D282A7B3F00AF3E16 /* Provider.swift */; }; - E0723379282A7B3F00AF3E16 /* BaseTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335E282A7B3F00AF3E16 /* BaseTarget.swift */; }; - E072337A282A7B3F00AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072335F282A7B3F00AF3E16 /* HTTPMethod.swift */; }; - E072337B282A7B3F00AF3E16 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723360282A7B3F00AF3E16 /* Response.swift */; }; - E072337C282A7B3F00AF3E16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723361282A7B3F00AF3E16 /* AppDelegate.swift */; }; - E072337D282A7B3F00AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723362282A7B3F00AF3E16 /* SceneDelegate.swift */; }; + E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723381282B7DB900AF3E16 /* HomeViewModel.swift */; }; + E07233AB282B7DB900AF3E16 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723382282B7DB900AF3E16 /* HomeViewController.swift */; }; + E07233AC282B7DB900AF3E16 /* RootWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723383282B7DB900AF3E16 /* RootWindow.swift */; }; + E07233AD282B7DB900AF3E16 /* PayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723385282B7DB900AF3E16 /* PayViewController.swift */; }; + E07233AE282B7DB900AF3E16 /* StarbucksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723387282B7DB900AF3E16 /* StarbucksViewController.swift */; }; + E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723389282B7DB900AF3E16 /* OrderDetailViewController.swift */; }; + E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072338B282B7DB900AF3E16 /* OrderListViewController.swift */; }; + E07233B1282B7DB900AF3E16 /* OrderTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072338D282B7DB900AF3E16 /* OrderTableViewDelegate.swift */; }; + E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072338E282B7DB900AF3E16 /* OrderTableViewDataSource.swift */; }; + E07233B3282B7DB900AF3E16 /* OrderCategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072338F282B7DB900AF3E16 /* OrderCategoryViewController.swift */; }; + E07233B4282B7DB900AF3E16 /* CategoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723390282B7DB900AF3E16 /* CategoryTableViewCell.swift */; }; + E07233B5282B7DB900AF3E16 /* OrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723391282B7DB900AF3E16 /* OrderViewModel.swift */; }; + E07233B6282B7DB900AF3E16 /* StarbucksRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723394282B7DB900AF3E16 /* StarbucksRepository.swift */; }; + E07233B7282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723395282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift */; }; + E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723396282B7DB900AF3E16 /* NetworkRepository.swift */; }; + E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723398282B7DB900AF3E16 /* UIColor+Extension.swift */; }; + E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723399282B7DB900AF3E16 /* Result+Extension.swift */; }; + E07233BB282B7DB900AF3E16 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072339B282B7DB900AF3E16 /* Log.swift */; }; + E07233BC282B7DB900AF3E16 /* CategoryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072339C282B7DB900AF3E16 /* CategoryMenu.swift */; }; + E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */; }; + E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A0282B7DB900AF3E16 /* StarbucksTarget.swift */; }; + E07233BF282B7DB900AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A2282B7DB900AF3E16 /* APIError.swift */; }; + E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A3282B7DB900AF3E16 /* HTTPContentType.swift */; }; + E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A4282B7DB900AF3E16 /* Provider.swift */; }; + E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A5282B7DB900AF3E16 /* BaseTarget.swift */; }; + E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A6282B7DB900AF3E16 /* HTTPMethod.swift */; }; + E07233C4282B7DB900AF3E16 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A7282B7DB900AF3E16 /* Response.swift */; }; + E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A8282B7DB900AF3E16 /* AppDelegate.swift */; }; + E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -61,33 +63,35 @@ E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbuckTargetTest.swift; sourceTree = ""; }; - E072333E282A7B3F00AF3E16 /* OrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewController.swift; sourceTree = ""; }; - E072333F282A7B3F00AF3E16 /* OrderTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDelegate.swift; sourceTree = ""; }; - E0723340282A7B3F00AF3E16 /* OrderTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDataSource.swift; sourceTree = ""; }; - E0723341282A7B3F00AF3E16 /* DetailOrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailOrderViewController.swift; sourceTree = ""; }; - E0723342282A7B3F00AF3E16 /* OrderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewCell.swift; sourceTree = ""; }; - E0723343282A7B3F00AF3E16 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; - E0723345282A7B3F00AF3E16 /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; - E0723346282A7B3F00AF3E16 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; - E0723347282A7B3F00AF3E16 /* RootWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; - E0723349282A7B3F00AF3E16 /* PayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; - E072334B282A7B3F00AF3E16 /* StarbucksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; - E072334E282A7B3F00AF3E16 /* StarbucksRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksRepository.swift; sourceTree = ""; }; - E072334F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksRepositoryImpl.swift; sourceTree = ""; }; - E0723350282A7B3F00AF3E16 /* NetworkRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkRepository.swift; sourceTree = ""; }; - E0723352282A7B3F00AF3E16 /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; - E0723353282A7B3F00AF3E16 /* Result+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Result+Extension.swift"; sourceTree = ""; }; - E0723355282A7B3F00AF3E16 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; - E0723357282A7B3F00AF3E16 /* StarbucksEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksEntity.swift; sourceTree = ""; }; - E0723359282A7B3F00AF3E16 /* StarbucksTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksTarget.swift; sourceTree = ""; }; - E072335B282A7B3F00AF3E16 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; - E072335C282A7B3F00AF3E16 /* HTTPContentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPContentType.swift; sourceTree = ""; }; - E072335D282A7B3F00AF3E16 /* Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; - E072335E282A7B3F00AF3E16 /* BaseTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTarget.swift; sourceTree = ""; }; - E072335F282A7B3F00AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; - E0723360282A7B3F00AF3E16 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; - E0723361282A7B3F00AF3E16 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - E0723362282A7B3F00AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + E0723381282B7DB900AF3E16 /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + E0723382282B7DB900AF3E16 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; + E0723383282B7DB900AF3E16 /* RootWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootWindow.swift; sourceTree = ""; }; + E0723385282B7DB900AF3E16 /* PayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayViewController.swift; sourceTree = ""; }; + E0723387282B7DB900AF3E16 /* StarbucksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksViewController.swift; sourceTree = ""; }; + E0723389282B7DB900AF3E16 /* OrderDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderDetailViewController.swift; sourceTree = ""; }; + E072338B282B7DB900AF3E16 /* OrderListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderListViewController.swift; sourceTree = ""; }; + E072338D282B7DB900AF3E16 /* OrderTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDelegate.swift; sourceTree = ""; }; + E072338E282B7DB900AF3E16 /* OrderTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTableViewDataSource.swift; sourceTree = ""; }; + E072338F282B7DB900AF3E16 /* OrderCategoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderCategoryViewController.swift; sourceTree = ""; }; + E0723390282B7DB900AF3E16 /* CategoryTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryTableViewCell.swift; sourceTree = ""; }; + E0723391282B7DB900AF3E16 /* OrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderViewModel.swift; sourceTree = ""; }; + E0723394282B7DB900AF3E16 /* StarbucksRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksRepository.swift; sourceTree = ""; }; + E0723395282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksRepositoryImpl.swift; sourceTree = ""; }; + E0723396282B7DB900AF3E16 /* NetworkRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkRepository.swift; sourceTree = ""; }; + E0723398282B7DB900AF3E16 /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + E0723399282B7DB900AF3E16 /* Result+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Result+Extension.swift"; sourceTree = ""; }; + E072339B282B7DB900AF3E16 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + E072339C282B7DB900AF3E16 /* CategoryMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryMenu.swift; sourceTree = ""; }; + E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksEntity.swift; sourceTree = ""; }; + E07233A0282B7DB900AF3E16 /* StarbucksTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksTarget.swift; sourceTree = ""; }; + E07233A2282B7DB900AF3E16 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; + E07233A3282B7DB900AF3E16 /* HTTPContentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPContentType.swift; sourceTree = ""; }; + E07233A4282B7DB900AF3E16 /* Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; + E07233A5282B7DB900AF3E16 /* BaseTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTarget.swift; sourceTree = ""; }; + E07233A6282B7DB900AF3E16 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + E07233A7282B7DB900AF3E16 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; + E07233A8282B7DB900AF3E16 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -147,7 +151,7 @@ E01B527828289D17009918AE /* Starbucks */ = { isa = PBXGroup; children = ( - E072333B282A7B3F00AF3E16 /* Sources */, + E072337E282B7DB900AF3E16 /* Sources */, E01B528F2828A0C3009918AE /* Resource */, E01B52902828A0CE009918AE /* Configuration */, ); @@ -178,132 +182,150 @@ path = API; sourceTree = ""; }; - E072333B282A7B3F00AF3E16 /* Sources */ = { + E072337E282B7DB900AF3E16 /* Sources */ = { isa = PBXGroup; children = ( - E0723356282A7B3F00AF3E16 /* Model */, - E072334C282A7B3F00AF3E16 /* Repository */, - E0723358282A7B3F00AF3E16 /* API */, - E072333C282A7B3F00AF3E16 /* Present */, - E0723351282A7B3F00AF3E16 /* Extension */, - E0723354282A7B3F00AF3E16 /* Common */, - E0723361282A7B3F00AF3E16 /* AppDelegate.swift */, - E0723362282A7B3F00AF3E16 /* SceneDelegate.swift */, + E072337F282B7DB900AF3E16 /* Present */, + E0723392282B7DB900AF3E16 /* Repository */, + E0723397282B7DB900AF3E16 /* Extension */, + E072339A282B7DB900AF3E16 /* Common */, + E072339D282B7DB900AF3E16 /* Model */, + E072339F282B7DB900AF3E16 /* API */, + E07233A8282B7DB900AF3E16 /* AppDelegate.swift */, + E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */, ); path = Sources; sourceTree = ""; }; - E072333C282A7B3F00AF3E16 /* Present */ = { + E072337F282B7DB900AF3E16 /* Present */ = { isa = PBXGroup; children = ( - E072333D282A7B3F00AF3E16 /* Order */, - E0723344282A7B3F00AF3E16 /* Home */, - E0723347282A7B3F00AF3E16 /* RootWindow.swift */, - E0723348282A7B3F00AF3E16 /* Pay */, - E072334A282A7B3F00AF3E16 /* TabBar */, + E0723380282B7DB900AF3E16 /* Home */, + E0723383282B7DB900AF3E16 /* RootWindow.swift */, + E0723384282B7DB900AF3E16 /* Pay */, + E0723386282B7DB900AF3E16 /* TabBar */, + E0723388282B7DB900AF3E16 /* OrderDetail */, + E072338A282B7DB900AF3E16 /* OrderList */, + E072338C282B7DB900AF3E16 /* OrderCategory */, ); path = Present; sourceTree = ""; }; - E072333D282A7B3F00AF3E16 /* Order */ = { + E0723380282B7DB900AF3E16 /* Home */ = { isa = PBXGroup; children = ( - E072333E282A7B3F00AF3E16 /* OrderViewController.swift */, - E072333F282A7B3F00AF3E16 /* OrderTableViewDelegate.swift */, - E0723340282A7B3F00AF3E16 /* OrderTableViewDataSource.swift */, - E0723341282A7B3F00AF3E16 /* DetailOrderViewController.swift */, - E0723342282A7B3F00AF3E16 /* OrderTableViewCell.swift */, - E0723343282A7B3F00AF3E16 /* OrderViewModel.swift */, - ); - path = Order; + E0723381282B7DB900AF3E16 /* HomeViewModel.swift */, + E0723382282B7DB900AF3E16 /* HomeViewController.swift */, + ); + path = Home; sourceTree = ""; }; - E0723344282A7B3F00AF3E16 /* Home */ = { + E0723384282B7DB900AF3E16 /* Pay */ = { isa = PBXGroup; children = ( - E0723345282A7B3F00AF3E16 /* HomeViewModel.swift */, - E0723346282A7B3F00AF3E16 /* HomeViewController.swift */, + E0723385282B7DB900AF3E16 /* PayViewController.swift */, ); - path = Home; + path = Pay; sourceTree = ""; }; - E0723348282A7B3F00AF3E16 /* Pay */ = { + E0723386282B7DB900AF3E16 /* TabBar */ = { isa = PBXGroup; children = ( - E0723349282A7B3F00AF3E16 /* PayViewController.swift */, + E0723387282B7DB900AF3E16 /* StarbucksViewController.swift */, ); - path = Pay; + path = TabBar; sourceTree = ""; }; - E072334A282A7B3F00AF3E16 /* TabBar */ = { + E0723388282B7DB900AF3E16 /* OrderDetail */ = { isa = PBXGroup; children = ( - E072334B282A7B3F00AF3E16 /* StarbucksViewController.swift */, + E0723389282B7DB900AF3E16 /* OrderDetailViewController.swift */, ); - path = TabBar; + path = OrderDetail; + sourceTree = ""; + }; + E072338A282B7DB900AF3E16 /* OrderList */ = { + isa = PBXGroup; + children = ( + E072338B282B7DB900AF3E16 /* OrderListViewController.swift */, + ); + path = OrderList; + sourceTree = ""; + }; + E072338C282B7DB900AF3E16 /* OrderCategory */ = { + isa = PBXGroup; + children = ( + E072338D282B7DB900AF3E16 /* OrderTableViewDelegate.swift */, + E072338E282B7DB900AF3E16 /* OrderTableViewDataSource.swift */, + E072338F282B7DB900AF3E16 /* OrderCategoryViewController.swift */, + E0723390282B7DB900AF3E16 /* CategoryTableViewCell.swift */, + E0723391282B7DB900AF3E16 /* OrderViewModel.swift */, + ); + path = OrderCategory; sourceTree = ""; }; - E072334C282A7B3F00AF3E16 /* Repository */ = { + E0723392282B7DB900AF3E16 /* Repository */ = { isa = PBXGroup; children = ( - E072334D282A7B3F00AF3E16 /* Starbucks */, - E0723350282A7B3F00AF3E16 /* NetworkRepository.swift */, + E0723393282B7DB900AF3E16 /* Starbucks */, + E0723396282B7DB900AF3E16 /* NetworkRepository.swift */, ); path = Repository; sourceTree = ""; }; - E072334D282A7B3F00AF3E16 /* Starbucks */ = { + E0723393282B7DB900AF3E16 /* Starbucks */ = { isa = PBXGroup; children = ( - E072334E282A7B3F00AF3E16 /* StarbucksRepository.swift */, - E072334F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift */, + E0723394282B7DB900AF3E16 /* StarbucksRepository.swift */, + E0723395282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift */, ); path = Starbucks; sourceTree = ""; }; - E0723351282A7B3F00AF3E16 /* Extension */ = { + E0723397282B7DB900AF3E16 /* Extension */ = { isa = PBXGroup; children = ( - E0723352282A7B3F00AF3E16 /* UIColor+Extension.swift */, - E0723353282A7B3F00AF3E16 /* Result+Extension.swift */, + E0723398282B7DB900AF3E16 /* UIColor+Extension.swift */, + E0723399282B7DB900AF3E16 /* Result+Extension.swift */, ); path = Extension; sourceTree = ""; }; - E0723354282A7B3F00AF3E16 /* Common */ = { + E072339A282B7DB900AF3E16 /* Common */ = { isa = PBXGroup; children = ( - E0723355282A7B3F00AF3E16 /* Log.swift */, + E072339B282B7DB900AF3E16 /* Log.swift */, + E072339C282B7DB900AF3E16 /* CategoryMenu.swift */, ); path = Common; sourceTree = ""; }; - E0723356282A7B3F00AF3E16 /* Model */ = { + E072339D282B7DB900AF3E16 /* Model */ = { isa = PBXGroup; children = ( - E0723357282A7B3F00AF3E16 /* StarbucksEntity.swift */, + E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */, ); path = Model; sourceTree = ""; }; - E0723358282A7B3F00AF3E16 /* API */ = { + E072339F282B7DB900AF3E16 /* API */ = { isa = PBXGroup; children = ( - E0723359282A7B3F00AF3E16 /* StarbucksTarget.swift */, - E072335A282A7B3F00AF3E16 /* Base */, + E07233A0282B7DB900AF3E16 /* StarbucksTarget.swift */, + E07233A1282B7DB900AF3E16 /* Base */, ); path = API; sourceTree = ""; }; - E072335A282A7B3F00AF3E16 /* Base */ = { + E07233A1282B7DB900AF3E16 /* Base */ = { isa = PBXGroup; children = ( - E072335B282A7B3F00AF3E16 /* APIError.swift */, - E072335C282A7B3F00AF3E16 /* HTTPContentType.swift */, - E072335D282A7B3F00AF3E16 /* Provider.swift */, - E072335E282A7B3F00AF3E16 /* BaseTarget.swift */, - E072335F282A7B3F00AF3E16 /* HTTPMethod.swift */, - E0723360282A7B3F00AF3E16 /* Response.swift */, + E07233A2282B7DB900AF3E16 /* APIError.swift */, + E07233A3282B7DB900AF3E16 /* HTTPContentType.swift */, + E07233A4282B7DB900AF3E16 /* Provider.swift */, + E07233A5282B7DB900AF3E16 /* BaseTarget.swift */, + E07233A6282B7DB900AF3E16 /* HTTPMethod.swift */, + E07233A7282B7DB900AF3E16 /* Response.swift */, ); path = Base; sourceTree = ""; @@ -529,33 +551,35 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E0723370282A7B3F00AF3E16 /* NetworkRepository.swift in Sources */, - E0723366282A7B3F00AF3E16 /* DetailOrderViewController.swift in Sources */, - E072336C282A7B3F00AF3E16 /* PayViewController.swift in Sources */, - E072336E282A7B3F00AF3E16 /* StarbucksRepository.swift in Sources */, - E072336D282A7B3F00AF3E16 /* StarbucksViewController.swift in Sources */, - E0723367282A7B3F00AF3E16 /* OrderTableViewCell.swift in Sources */, - E0723369282A7B3F00AF3E16 /* HomeViewModel.swift in Sources */, - E072336A282A7B3F00AF3E16 /* HomeViewController.swift in Sources */, - E072337C282A7B3F00AF3E16 /* AppDelegate.swift in Sources */, - E0723373282A7B3F00AF3E16 /* Log.swift in Sources */, - E0723372282A7B3F00AF3E16 /* Result+Extension.swift in Sources */, - E072337D282A7B3F00AF3E16 /* SceneDelegate.swift in Sources */, - E0723363282A7B3F00AF3E16 /* OrderViewController.swift in Sources */, - E0723371282A7B3F00AF3E16 /* UIColor+Extension.swift in Sources */, - E0723368282A7B3F00AF3E16 /* OrderViewModel.swift in Sources */, - E0723376282A7B3F00AF3E16 /* APIError.swift in Sources */, - E0723379282A7B3F00AF3E16 /* BaseTarget.swift in Sources */, - E0723374282A7B3F00AF3E16 /* StarbucksEntity.swift in Sources */, - E072336B282A7B3F00AF3E16 /* RootWindow.swift in Sources */, - E0723377282A7B3F00AF3E16 /* HTTPContentType.swift in Sources */, - E0723364282A7B3F00AF3E16 /* OrderTableViewDelegate.swift in Sources */, - E0723378282A7B3F00AF3E16 /* Provider.swift in Sources */, - E072337A282A7B3F00AF3E16 /* HTTPMethod.swift in Sources */, - E072336F282A7B3F00AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, - E0723365282A7B3F00AF3E16 /* OrderTableViewDataSource.swift in Sources */, - E0723375282A7B3F00AF3E16 /* StarbucksTarget.swift in Sources */, - E072337B282A7B3F00AF3E16 /* Response.swift in Sources */, + E07233BC282B7DB900AF3E16 /* CategoryMenu.swift in Sources */, + E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */, + E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */, + E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, + E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */, + E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */, + E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */, + E07233BB282B7DB900AF3E16 /* Log.swift in Sources */, + E07233B1282B7DB900AF3E16 /* OrderTableViewDelegate.swift in Sources */, + E07233B7282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, + E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */, + E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, + E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */, + E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */, + E07233AC282B7DB900AF3E16 /* RootWindow.swift in Sources */, + E07233C4282B7DB900AF3E16 /* Response.swift in Sources */, + E07233B5282B7DB900AF3E16 /* OrderViewModel.swift in Sources */, + E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */, + E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, + E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, + E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */, + E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */, + E07233B4282B7DB900AF3E16 /* CategoryTableViewCell.swift in Sources */, + E07233AE282B7DB900AF3E16 /* StarbucksViewController.swift in Sources */, + E07233AB282B7DB900AF3E16 /* HomeViewController.swift in Sources */, + E07233AD282B7DB900AF3E16 /* PayViewController.swift in Sources */, + E07233B3282B7DB900AF3E16 /* OrderCategoryViewController.swift in Sources */, + E07233B6282B7DB900AF3E16 /* StarbucksRepository.swift in Sources */, + E07233BF282B7DB900AF3E16 /* APIError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From d22397dcaedb39409edb788ca8374a8483d1b858 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 14:37:14 +0900 Subject: [PATCH 21/57] =?UTF-8?q?[STAR-14]=20feature:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20Json=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/.DS_Store | Bin 6148 -> 0 bytes Starbucks/Starbucks.xcodeproj/project.pbxproj | 8 +++ Starbucks/Starbucks/Resource/Category.json | 62 ++++++++++++++++++ .../Sources/Model/CategoryEntity.swift | 26 ++++++++ 4 files changed, 96 insertions(+) delete mode 100644 Starbucks/.DS_Store create mode 100644 Starbucks/Starbucks/Resource/Category.json create mode 100644 Starbucks/Starbucks/Sources/Model/CategoryEntity.swift diff --git a/Starbucks/.DS_Store b/Starbucks/.DS_Store deleted file mode 100644 index 7f3b31b970a16ecc8fc03c68f1349000c6bcd8e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&1%~~5T13^dfm|CLjpN2^cq^{2bYqI>bTbss?Z+V;z*WAL}aaybi%2B40V?Vzm-h(}o{eF290u|M!2c|8J6P z&njRQSSSUU)A#y4Ov&7>bIH+NE6@+o$!J}zQBkll*RcxdDqcfZhI4`fMAu-g5l7JM O9|0wUZL9(hs=zN~O^!zZ diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index f72662f..a9ac01a 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ E07233C4282B7DB900AF3E16 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A7282B7DB900AF3E16 /* Response.swift */; }; E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A8282B7DB900AF3E16 /* AppDelegate.swift */; }; E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */; }; + E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */; }; + E07233CA282B82BF00AF3E16 /* Category.json in Resources */ = {isa = PBXBuildFile; fileRef = E07233C9282B82BF00AF3E16 /* Category.json */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -92,6 +94,8 @@ E07233A7282B7DB900AF3E16 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; E07233A8282B7DB900AF3E16 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryEntity.swift; sourceTree = ""; }; + E07233C9282B82BF00AF3E16 /* Category.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Category.json; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -162,6 +166,7 @@ isa = PBXGroup; children = ( E01B528228289D18009918AE /* Assets.xcassets */, + E07233C9282B82BF00AF3E16 /* Category.json */, ); path = Resource; sourceTree = ""; @@ -304,6 +309,7 @@ isa = PBXGroup; children = ( E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */, + E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */, ); path = Model; sourceTree = ""; @@ -435,6 +441,7 @@ buildActionMask = 2147483647; files = ( E01B528328289D18009918AE /* Assets.xcassets in Resources */, + E07233CA282B82BF00AF3E16 /* Category.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -555,6 +562,7 @@ E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */, E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */, E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, + E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */, E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */, E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */, E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */, diff --git a/Starbucks/Starbucks/Resource/Category.json b/Starbucks/Starbucks/Resource/Category.json new file mode 100644 index 0000000..d659bf3 --- /dev/null +++ b/Starbucks/Starbucks/Resource/Category.json @@ -0,0 +1,62 @@ +{ + "beverageMenu": [ + { + "groupId": "W0000171", + "title": "콜드브루", + "subTitle": "Cold Brew", + "imagePath": "" + }, + { + "groupId": "W0000003", + "title": "에스프레소", + "subTitle": "Espresso", + "imagePath": "" + }, + { + "groupId": "W0000004", + "title": "프라프치노", + "subTitle": "Frappuccino", + "imagePath": "" + } + ], + "foodMenu": [ + { + "groupId": "W0000013", + "title": "빵", + "subTitle": "Cold Brew", + "imagePath": "" + }, + { + "groupId": "W0000032", + "title": "케이크", + "subTitle": "Espresso", + "imagePath": "" + }, + { + "groupId": "W0000033", + "title": "샌드위치", + "subTitle": "Frappuccino", + "imagePath": "" + } + ], + "productMenu": [ + { + "groupId": "W0000030", + "title": "머그컵", + "subTitle": "Cold Brew", + "imagePath": "" + }, + { + "groupId": "W0000164", + "title": "유리잔", + "subTitle": "Espresso", + "imagePath": "" + }, + { + "groupId": "W0000031", + "title": "텀블러", + "subTitle": "Frappuccino", + "imagePath": "" + } + ] +} diff --git a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift new file mode 100644 index 0000000..486359f --- /dev/null +++ b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift @@ -0,0 +1,26 @@ +// +// CategoryEntity.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import Foundation + +struct CategoryEntity {} + +extension CategoryEntity { + + struct Category: Codable { + let beverageMenu: [Group] + let foodMenu: [Group] + let productMenu: [Group] + } + + struct Group: Codable { + let groupId: String + let title: String + let subTitle: String + let imagePath: String + } +} From 923125190bde1768716bbd26a197d26fd03565f7 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 14:53:12 +0900 Subject: [PATCH 22/57] =?UTF-8?q?[STAR-14]=20feature:=20=EC=83=98=ED=94=8C?= =?UTF-8?q?=20json=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 4 - Starbucks/Starbucks/Resource/Category.json | 93 +++++++++++++------ .../Sources/Common/CategoryMenu.swift | 74 --------------- .../Sources/Model/CategoryEntity.swift | 25 +++-- .../OrderCategoryViewController.swift | 6 +- .../OrderTableViewDataSource.swift | 8 +- .../OrderCategory/OrderViewModel.swift | 14 ++- 7 files changed, 99 insertions(+), 125 deletions(-) delete mode 100644 Starbucks/Starbucks/Sources/Common/CategoryMenu.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index a9ac01a..1e6c4d5 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -29,7 +29,6 @@ E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723398282B7DB900AF3E16 /* UIColor+Extension.swift */; }; E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723399282B7DB900AF3E16 /* Result+Extension.swift */; }; E07233BB282B7DB900AF3E16 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072339B282B7DB900AF3E16 /* Log.swift */; }; - E07233BC282B7DB900AF3E16 /* CategoryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072339C282B7DB900AF3E16 /* CategoryMenu.swift */; }; E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */; }; E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A0282B7DB900AF3E16 /* StarbucksTarget.swift */; }; E07233BF282B7DB900AF3E16 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A2282B7DB900AF3E16 /* APIError.swift */; }; @@ -83,7 +82,6 @@ E0723398282B7DB900AF3E16 /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; E0723399282B7DB900AF3E16 /* Result+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Result+Extension.swift"; sourceTree = ""; }; E072339B282B7DB900AF3E16 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; - E072339C282B7DB900AF3E16 /* CategoryMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryMenu.swift; sourceTree = ""; }; E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksEntity.swift; sourceTree = ""; }; E07233A0282B7DB900AF3E16 /* StarbucksTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarbucksTarget.swift; sourceTree = ""; }; E07233A2282B7DB900AF3E16 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; @@ -300,7 +298,6 @@ isa = PBXGroup; children = ( E072339B282B7DB900AF3E16 /* Log.swift */, - E072339C282B7DB900AF3E16 /* CategoryMenu.swift */, ); path = Common; sourceTree = ""; @@ -558,7 +555,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E07233BC282B7DB900AF3E16 /* CategoryMenu.swift in Sources */, E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */, E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */, E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, diff --git a/Starbucks/Starbucks/Resource/Category.json b/Starbucks/Starbucks/Resource/Category.json index d659bf3..c0ac7e9 100644 --- a/Starbucks/Starbucks/Resource/Category.json +++ b/Starbucks/Starbucks/Resource/Category.json @@ -1,62 +1,103 @@ { - "beverageMenu": [ + "groups": [ { "groupId": "W0000171", "title": "콜드브루", "subTitle": "Cold Brew", - "imagePath": "" + "imagePath": "", + "category": { + "beverage": { + + } + } }, { "groupId": "W0000003", "title": "에스프레소", "subTitle": "Espresso", - "imagePath": "" + "imagePath": "", + "category": { + "beverage": { + + } + } }, { "groupId": "W0000004", "title": "프라프치노", "subTitle": "Frappuccino", - "imagePath": "" - } - ], - "foodMenu": [ + "imagePath": "", + "category": { + "beverage": { + + } + } + }, { - "groupId": "W0000013", - "title": "빵", + "groupId": "W0000171", + "title": "브레드", "subTitle": "Cold Brew", - "imagePath": "" + "imagePath": "", + "category": { + "beverage": { + + } + } }, { - "groupId": "W0000032", + "groupId": "W0000003", "title": "케이크", "subTitle": "Espresso", - "imagePath": "" + "imagePath": "", + "category": { + "beverage": { + + } + } }, { - "groupId": "W0000033", + "groupId": "W0000004", "title": "샌드위치", "subTitle": "Frappuccino", - "imagePath": "" - } - ], - "productMenu": [ + "imagePath": "", + "category": { + "beverage": { + + } + } + }, { - "groupId": "W0000030", - "title": "머그컵", + "groupId": "W0000171", + "title": "머그", "subTitle": "Cold Brew", - "imagePath": "" + "imagePath": "", + "category": { + "beverage": { + + } + } }, { - "groupId": "W0000164", - "title": "유리잔", + "groupId": "W0000003", + "title": "글라스", "subTitle": "Espresso", - "imagePath": "" + "imagePath": "", + "category": { + "beverage": { + + } + } }, { - "groupId": "W0000031", - "title": "텀블러", + "groupId": "W0000004", + "title": "텀플러", "subTitle": "Frappuccino", - "imagePath": "" + "imagePath": "", + "category": { + "beverage": { + + } + } } ] } diff --git a/Starbucks/Starbucks/Sources/Common/CategoryMenu.swift b/Starbucks/Starbucks/Sources/Common/CategoryMenu.swift deleted file mode 100644 index 190be96..0000000 --- a/Starbucks/Starbucks/Sources/Common/CategoryMenu.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// CategoryMenu.swift -// Starbucks -// -// Created by YEONGJIN JANG on 2022/05/10. -// - -import Foundation - -protocol Menus { - var name: String { get } -} - -enum BeverageMenu: Menus, CaseIterable { - case coldBrew, espresso, frappuccino, gucci - - var name: String { - switch self { - case .coldBrew: - return "콜드브루" - case .espresso: - return "에스프레소" - case .frappuccino: - return "프라푸치노" - case .gucci: - return "구찌탕" - } - } -} - -enum FoodMenu: Menus, CaseIterable { - case bread, cake, sandwich - - var name: String { - switch self { - case .bread: - return "빵" - case .cake: - return "케이크" - case .sandwich: - return "샌드위치" - } - } -} - -enum ProductMenu: Menus, CaseIterable { - case mug, glass, tumbler - - var name: String { - switch self { - case .mug: - return "머그컵" - case .glass: - return "유리잔" - case .tumbler: - return "텀블러" - } - } -} - -enum Category: CaseIterable { - case beverage, food, product - - var name: String { - switch self { - case .beverage: - return "음료" - case .food: - return "푸드" - case .product: - return "상품" - } - } -} diff --git a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift index 486359f..1f46399 100644 --- a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift @@ -7,20 +7,33 @@ import Foundation -struct CategoryEntity {} +struct Category: Codable { + let groups: [Group] +} -extension CategoryEntity { +extension Category { - struct Category: Codable { - let beverageMenu: [Group] - let foodMenu: [Group] - let productMenu: [Group] + enum GroupType: Codable, CaseIterable { + case beverage, food, product + + var name: String { + switch self { + case .beverage: + return "음료" + case .food: + return "푸드" + case .product: + return "상품" + } + } } struct Group: Codable { + let category: Category.GroupType let groupId: String let title: String let subTitle: String let imagePath: String } } + diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index 19290ea..8a20943 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -41,7 +41,7 @@ class OrderCategoryViewController: UIViewController { }() private let categoryButtons: [UIButton] = { - Category.allCases.map { + Category.GroupType.allCases.map { let button = UIButton() button.setTitle($0.name, for: .normal) button.setTitleColor(.systemGray, for: .normal) @@ -67,7 +67,7 @@ class OrderCategoryViewController: UIViewController { private func bind() { rx.viewDidLoad - .map { _ in Category.beverage } + .map { _ in .beverage } .bind(to: viewModel.action().loadCategory) .disposed(by: disposeBag) @@ -131,7 +131,7 @@ class OrderCategoryViewController: UIViewController { } extension OrderCategoryViewController { - private func updateDatasource(menu: [Menus]) { + private func updateDatasource(menu: [Category.Group]) { self.tableViewDataSource = OrderTableViewDataSource(menus: menu) DispatchQueue.main.async { self.tableView.dataSource = self.tableViewDataSource diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift index c41276d..2d83ed7 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift @@ -11,9 +11,9 @@ import RxSwift class OrderTableViewDataSource: NSObject, UITableViewDataSource { - private let menus: [Menus] + private let menus: [Category.Group] - init(menus: [Menus]) { + init(menus: [Category.Group]) { self.menus = menus super.init() } @@ -27,8 +27,8 @@ class OrderTableViewDataSource: NSObject, UITableViewDataSource { as? CategoryTableViewCell else { return UITableViewCell() } - cell.setMenuName(text: menus[indexPath.row].name) - cell.setSubName(text: menus[indexPath.row].name) + cell.setMenuName(text: menus[indexPath.row].title) + cell.setSubName(text: menus[indexPath.row].subTitle) return cell } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index eadb5dc..864ddeb 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -10,12 +10,12 @@ import RxRelay import RxSwift protocol OrderViewModelAction { - var loadCategory: BehaviorRelay { get } + var loadCategory: BehaviorRelay { get } var loadCategoryList: PublishRelay { get } } protocol OrderViewModelState { - var loadedCategory: PublishRelay<[Menus]> { get } + var loadedCategory: PublishRelay<[Category.Group]> { get } } protocol OrderViewModelBinding { @@ -29,21 +29,19 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB func action() -> OrderViewModelAction { self } - let loadCategory = BehaviorRelay(value: .beverage) + let loadCategory = BehaviorRelay(value: .beverage) let loadCategoryList = PublishRelay() func state() -> OrderViewModelState { self } - let loadedCategory = PublishRelay<[Menus]>() + let loadedCategory = PublishRelay<[Category.Group]>() private let disposeBag = DisposeBag() - private let categoryMenu: [Category : [Menus]] = [.beverage : BeverageMenu.allCases, - .food : FoodMenu.allCases, - .product : ProductMenu.allCases] + private var categoryMenu: [Category.GroupType : [Category.Group]]? init() { loadCategory - .compactMap { self.categoryMenu[$0] } + .compactMap { self.categoryMenu?[$0] } .bind(to: loadedCategory) .disposed(by: disposeBag) From ca594b5bd64932248c69fb08a84fcfabb928f57c Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 14:57:57 +0900 Subject: [PATCH 23/57] =?UTF-8?q?json=20=ED=8C=8C=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks/Resource/Category.json | 54 ++++--------------- .../Sources/Model/CategoryEntity.swift | 7 +-- 2 files changed, 13 insertions(+), 48 deletions(-) diff --git a/Starbucks/Starbucks/Resource/Category.json b/Starbucks/Starbucks/Resource/Category.json index c0ac7e9..4a4e12b 100644 --- a/Starbucks/Starbucks/Resource/Category.json +++ b/Starbucks/Starbucks/Resource/Category.json @@ -5,99 +5,63 @@ "title": "콜드브루", "subTitle": "Cold Brew", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "BEVERAGE" }, { "groupId": "W0000003", "title": "에스프레소", "subTitle": "Espresso", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "BEVERAGE" }, { "groupId": "W0000004", "title": "프라프치노", "subTitle": "Frappuccino", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "BEVERAGE" }, { "groupId": "W0000171", "title": "브레드", "subTitle": "Cold Brew", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "FOOD" }, { "groupId": "W0000003", "title": "케이크", "subTitle": "Espresso", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "FOOD" }, { "groupId": "W0000004", "title": "샌드위치", "subTitle": "Frappuccino", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "FOOD" }, { "groupId": "W0000171", "title": "머그", "subTitle": "Cold Brew", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "PRODUCT" }, { "groupId": "W0000003", "title": "글라스", "subTitle": "Espresso", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "PRODUCT" }, { "groupId": "W0000004", "title": "텀플러", "subTitle": "Frappuccino", "imagePath": "", - "category": { - "beverage": { - - } - } + "category": "PRODUCT" } ] } diff --git a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift index 1f46399..27cf228 100644 --- a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift @@ -13,8 +13,10 @@ struct Category: Codable { extension Category { - enum GroupType: Codable, CaseIterable { - case beverage, food, product + enum GroupType: String, Codable, CaseIterable { + case beverage = "BEVERAGE" + case food = "FOOD" + case product = "PRODUCT" var name: String { switch self { @@ -36,4 +38,3 @@ extension Category { let imagePath: String } } - From 88766d9c456d5c294f835adeea442250183fc7fa Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Wed, 11 May 2022 16:45:22 +0900 Subject: [PATCH 24/57] =?UTF-8?q?[STAR-16]=20feat:=20Category.json=20Mock?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=ED=95=98=EC=97=AC,=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=EC=A0=81=EC=9C=BC=EB=A1=9C=20Parsing?= =?UTF-8?q?=ED=95=9C=20data=EB=A1=9C=20CategoryMenu=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 4 +- .../OrderCategory/OrderViewModel.swift | 48 ++++++++++++++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index 1e6c4d5..b415405 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -657,7 +657,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.4; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -711,7 +711,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.4; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 864ddeb..a3ba536 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -35,21 +35,45 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB func state() -> OrderViewModelState { self } let loadedCategory = PublishRelay<[Category.Group]>() - private let disposeBag = DisposeBag() - private var categoryMenu: [Category.GroupType : [Category.Group]]? + private var categoryMenu: [Category.GroupType : [Category.Group]] = [:] init() { - loadCategory - .compactMap { self.categoryMenu?[$0] } - .bind(to: loadedCategory) - .disposed(by: disposeBag) + + guard let data = parseJsonFile(fileName: "Category", format: "json") else { return } + + do { + let category = try JSONDecoder().decode(Category.self, from: data) + + category.groups.forEach { item in //item => Group + categoryMenu[item.category] = category.groups + .filter { $0.category == item.category } + } + } catch { + return + } +// +// loadCategory +// .compactMap { self.categoryMenu[$0] } +// .bind(to: loadedCategory) +// .disposed(by: disposeBag) +// +// Observable +// .combineLatest(loadCategory, loadCategoryList) +// .bind(onNext: { category, menuIndex in +// // TODO: - List Category 뷰로 연결하는 로직 +// }) +// .disposed(by: disposeBag) + } + + private func parseJsonFile(fileName: String, format: String) -> Data? { + guard let fileLocation = Bundle.main.url(forResource: fileName, withExtension: format) else { return nil } - Observable - .combineLatest(loadCategory, loadCategoryList) - .bind(onNext: { category, menuIndex in - // TODO: - List Category 뷰로 연결하는 로직 - }) - .disposed(by: disposeBag) + do { + let data = try Data(contentsOf: fileLocation) + return data + } catch { + return nil + } } } From 5a450591ffea27d709d604cfb1bdd1d64d72ea6a Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Wed, 11 May 2022 18:16:50 +0900 Subject: [PATCH 25/57] =?UTF-8?q?[STAR-17]=20feat:=20Category=20Buttons=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=84=A0=ED=83=9D=EC=8B=9C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EA=B0=95=EC=A1=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Model/CategoryEntity.swift | 24 ++++++++++ .../OrderCategoryViewController.swift | 19 ++++++++ .../OrderCategory/OrderViewModel.swift | 45 +++++++++++-------- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift index 27cf228..93ad712 100644 --- a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift @@ -28,6 +28,30 @@ extension Category { return "상품" } } + + var index: Int { + switch self { + case .beverage: + return 0 + case .food: + return 1 + case .product: + return 2 + } + } + + static func indexToCase(_ index: Int) -> GroupType? { + switch index { + case 0: + return .beverage + case 1: + return .food + case 2: + return .product + default: + return nil + } + } } struct Group: Codable { diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index 8a20943..fd2b872 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -44,6 +44,7 @@ class OrderCategoryViewController: UIViewController { Category.GroupType.allCases.map { let button = UIButton() button.setTitle($0.name, for: .normal) + button.setTitleColor(.black, for: .selected) button.setTitleColor(.systemGray, for: .normal) return button } @@ -80,6 +81,24 @@ class OrderCategoryViewController: UIViewController { tableViewHandler.selectedCellIndex .bind(to: viewModel.action().loadCategoryList) .disposed(by: disposeBag) + + viewModel.state().selectedCategory + .map { $0.index } + .bind(onNext: { selectIndex in + self.categoryButtons.enumerated().forEach { index, button in + button.isSelected = index == selectIndex + } + }) + .disposed(by: disposeBag) + + categoryButtons.enumerated().forEach { index, button in + button.rx.tap + .compactMap { _ in + Category.GroupType.indexToCase(index) + } + .bind(to: viewModel.action().loadCategory) + .disposed(by: disposeBag) + } } private func attribute() { diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index a3ba536..1a13f68 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -16,6 +16,7 @@ protocol OrderViewModelAction { protocol OrderViewModelState { var loadedCategory: PublishRelay<[Category.Group]> { get } + var selectedCategory: PublishRelay { get } } protocol OrderViewModelBinding { @@ -28,42 +29,48 @@ typealias OrderViewModelProtocol = OrderViewModelBinding class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelBinding { func action() -> OrderViewModelAction { self } - + let loadCategory = BehaviorRelay(value: .beverage) let loadCategoryList = PublishRelay() func state() -> OrderViewModelState { self } let loadedCategory = PublishRelay<[Category.Group]>() + let selectedCategory = PublishRelay() private let disposeBag = DisposeBag() - private var categoryMenu: [Category.GroupType : [Category.Group]] = [:] + private var categoryMenu = Category.GroupType.allCases.reduce(into: [Category.GroupType: [Category.Group]]()) { + $0[$1] = [] + } init() { + load() + + loadCategory + .compactMap { self.categoryMenu[$0] } + .do { _ in + self.selectedCategory.accept(self.loadCategory.value) + } + .bind(to: loadedCategory) + .disposed(by: disposeBag) + + Observable + .combineLatest(loadCategory, loadCategoryList) + .bind(onNext: { category, menuIndex in + }) + .disposed(by: disposeBag) + } + private func load() { guard let data = parseJsonFile(fileName: "Category", format: "json") else { return } - + do { let category = try JSONDecoder().decode(Category.self, from: data) - - category.groups.forEach { item in //item => Group - categoryMenu[item.category] = category.groups - .filter { $0.category == item.category } + category.groups.forEach { + categoryMenu[$0.category]?.append($0) } } catch { return } -// -// loadCategory -// .compactMap { self.categoryMenu[$0] } -// .bind(to: loadedCategory) -// .disposed(by: disposeBag) -// -// Observable -// .combineLatest(loadCategory, loadCategoryList) -// .bind(onNext: { category, menuIndex in -// // TODO: - List Category 뷰로 연결하는 로직 -// }) -// .disposed(by: disposeBag) } private func parseJsonFile(fileName: String, format: String) -> Data? { From 6890e2474a05f4f96a05e93bfbe762530bdc9bfe Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 18:25:54 +0900 Subject: [PATCH 26/57] =?UTF-8?q?[STAR-14]=20feature:=20=EB=A0=88=ED=8C=8C?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=EB=A1=9C=20json=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/Home/HomeViewController.swift | 19 +++++++++++++++++++ .../Sources/Present/Home/HomeViewModel.swift | 2 ++ .../TabBar/StarbucksViewController.swift | 2 +- .../Starbucks/StarbucksRepository.swift | 1 + .../Starbucks/StarbucksRepositoryImpl.swift | 17 +++++++++++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index e8b330e..8bdb272 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -10,6 +10,15 @@ import RxSwift import UIKit class HomeViewController: UIViewController { + + private let scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.isPagingEnabled = false + scrollView.backgroundColor = .blue + scrollView.showsHorizontalScrollIndicator = true + return scrollView + }() + private let viewModel: HomeViewModelProtocol private let disposeBag = DisposeBag() @@ -36,5 +45,15 @@ class HomeViewController: UIViewController { } private func layout() { + view.addSubview(scrollView) + + scrollView.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide) + $0.leading.trailing.bottom.equalToSuperview() + } + scrollView.contentLayoutGuide.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(1000) + } } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 706a3b6..3d1eab7 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -15,6 +15,7 @@ protocol HomeViewModelAction { } protocol HomeViewModelState { + var loadEvent: PublishRelay { get } } protocol HomeViewModelBinding { @@ -47,6 +48,7 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta requestHome .compactMap { $0.value } .bind(onNext: { + state().loadEvent.accept(<#T##event: Void##Void#>) }) .disposed(by: disposeBag) diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index a7b72d2..b60cf8d 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -17,7 +17,7 @@ class StarbucksViewController: UITabBarController { } private func setUpTabBar() { - let homeViewController = HomeViewController(viewModel: HomeViewModel()) + let homeViewController = UINavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())) homeViewController.tabBarItem.title = "Home" homeViewController.tabBarItem.image = UIImage(named: "ic_temp") diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift index a06383a..083dd35 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -11,4 +11,5 @@ import RxSwift protocol StarbucksRepository { func requestHome() -> Single> func requestEvent() -> Single> + func requestCategory() -> Single> } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift index 5d5ddb8..0e8d5b9 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -20,4 +20,21 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo .request(.requestEvent) .map(StarbucksEntity.HomeEvent.self) } + + func requestCategory() -> Single> { + Single.create { observer in + guard let url = Bundle.main.url(forResource: "Category", withExtension: "json"), + let data = try? Data(contentsOf: url), + let category = try? JSONDecoder().decode(Category.self, from: data) else { + + let response = Response(statusCode: -999, data: Data()) + let error = APIError.jsonMapping(response: response) + observer(.success(.failure(error))) + return Disposables.create { } + } + + observer(.success(.success(category.groups))) + return Disposables.create { } + } + } } From c5f565a6b43fd92d642b6f5ee5d14b09371a92d8 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 19:27:20 +0900 Subject: [PATCH 27/57] =?UTF-8?q?[STAR-14]=20feature:=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=ED=95=99=EC=8A=B5=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Present/Home/HomeViewModel.swift | 1 - .../OrderCategoryViewController.swift | 5 +- .../OrderCategory/OrderViewModel.swift | 80 ++++++++++--------- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 3d1eab7..7b4d7f7 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -48,7 +48,6 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta requestHome .compactMap { $0.value } .bind(onNext: { - state().loadEvent.accept(<#T##event: Void##Void#>) }) .disposed(by: disposeBag) diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index fd2b872..5cea466 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -68,7 +68,6 @@ class OrderCategoryViewController: UIViewController { private func bind() { rx.viewDidLoad - .map { _ in .beverage } .bind(to: viewModel.action().loadCategory) .disposed(by: disposeBag) @@ -79,7 +78,7 @@ class OrderCategoryViewController: UIViewController { .disposed(by: disposeBag) tableViewHandler.selectedCellIndex - .bind(to: viewModel.action().loadCategoryList) + .bind(to: viewModel.action().tappedMenu) .disposed(by: disposeBag) viewModel.state().selectedCategory @@ -96,7 +95,7 @@ class OrderCategoryViewController: UIViewController { .compactMap { _ in Category.GroupType.indexToCase(index) } - .bind(to: viewModel.action().loadCategory) + .bind(to: viewModel.action().tappedCategory) .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 1a13f68..8aa6776 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -10,13 +10,15 @@ import RxRelay import RxSwift protocol OrderViewModelAction { - var loadCategory: BehaviorRelay { get } - var loadCategoryList: PublishRelay { get } + var loadCategory: PublishRelay { get } + var tappedCategory: PublishRelay { get } + var tappedMenu: PublishRelay { get } } protocol OrderViewModelState { var loadedCategory: PublishRelay<[Category.Group]> { get } var selectedCategory: PublishRelay { get } + var selectedMenu: PublishRelay { get } } protocol OrderViewModelBinding { @@ -30,12 +32,17 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB func action() -> OrderViewModelAction { self } - let loadCategory = BehaviorRelay(value: .beverage) - let loadCategoryList = PublishRelay() + let loadCategory = PublishRelay() + let tappedCategory = PublishRelay() + let tappedMenu = PublishRelay() func state() -> OrderViewModelState { self } + let loadedCategory = PublishRelay<[Category.Group]>() let selectedCategory = PublishRelay() + let selectedMenu = PublishRelay() + + private var starbucksRepository = StarbucksRepositoryImpl() private let disposeBag = DisposeBag() private var categoryMenu = Category.GroupType.allCases.reduce(into: [Category.GroupType: [Category.Group]]()) { @@ -43,44 +50,43 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB } init() { - load() - - loadCategory - .compactMap { self.categoryMenu[$0] } - .do { _ in - self.selectedCategory.accept(self.loadCategory.value) + + //MARK: Repository에서 카테고리 Json 파일을 로드 + let requestCategory = action().loadCategory + .withUnretained(self) + .flatMapLatest { model, _ in + model.starbucksRepository.requestCategory() } - .bind(to: loadedCategory) + .share() + + //로드가 성공하면 여기 로직 + requestCategory + .compactMap { result in result.value } //값이 있는지 확인 + .map { groups in + groups.reduce(into: self.categoryMenu) { category, group in + category[group.category]?.append(group) + } } //값이 있으면 Array -> dic로 변환 + .withUnretained(self) //weak self를 생략하기 위한 오퍼레이터 + .do { model, groups in model.categoryMenu = groups } //모델의 dic에 값을 넣어주고 + .map { _ in .beverage } //처음 보여질 테이블은 beverage이므로 값을 넘겨줌 + .bind(to: tappedCategory) //tappedCategory 퍼블리시 실행 .disposed(by: disposeBag) - + + //로드가 실패하면 여기 로직 Observable - .combineLatest(loadCategory, loadCategoryList) - .bind(onNext: { category, menuIndex in + .merge( + requestCategory.compactMap { $0.error } + ) + .bind(onNext: { + //TODO: error 처리 }) .disposed(by: disposeBag) - } - - private func load() { - guard let data = parseJsonFile(fileName: "Category", format: "json") else { return } - - do { - let category = try JSONDecoder().decode(Category.self, from: data) - category.groups.forEach { - categoryMenu[$0.category]?.append($0) - } - } catch { - return - } - } - - private func parseJsonFile(fileName: String, format: String) -> Data? { - guard let fileLocation = Bundle.main.url(forResource: fileName, withExtension: format) else { return nil } - do { - let data = try Data(contentsOf: fileLocation) - return data - } catch { - return nil - } + action().tappedCategory //카테고리 응답 오면 시작 + .withUnretained(self) + .do { model, type in model.selectedCategory.accept(type) } //선택한 카테고리 이벤트 알림 + .compactMap { model, type in model.categoryMenu[type] } //선택한 카테고리 데이터 반환 + .bind(to: loadedCategory) //선택한 카테고리 데이터 전달 + .disposed(by: disposeBag) } } From 07c5f990429de98884736d3aad757d00d598207a Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 20:24:48 +0900 Subject: [PATCH 28/57] =?UTF-8?q?[STAR-18]=20feature:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=A1=9C=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 20 +++++++ Starbucks/Starbucks/Resource/Category.json | 30 +++++------ .../Starbucks/Sources/Common/Container.swift | 18 +++++++ .../Sources/Common/ImageManager.swift | 52 +++++++++++++++++++ .../Common/PropertyWrapper/Inject.swift | 19 +++++++ .../Sources/Model/CategoryEntity.swift | 2 +- .../Sources/Present/Home/HomeViewModel.swift | 4 +- .../OrderCategory/CategoryTableViewCell.swift | 20 ++++++- .../OrderTableViewDataSource.swift | 3 +- .../OrderCategory/OrderViewModel.swift | 9 ++-- 10 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Common/Container.swift create mode 100644 Starbucks/Starbucks/Sources/Common/ImageManager.swift create mode 100644 Starbucks/Starbucks/Sources/Common/PropertyWrapper/Inject.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index b415405..7765686 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -41,6 +41,9 @@ E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */; }; E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */; }; E07233CA282B82BF00AF3E16 /* Category.json in Resources */ = {isa = PBXBuildFile; fileRef = E07233C9282B82BF00AF3E16 /* Category.json */; }; + E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D0282BCB5E00AF3E16 /* Container.swift */; }; + E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D3282BCBBC00AF3E16 /* Inject.swift */; }; + E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D5282BCC8100AF3E16 /* ImageManager.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -94,6 +97,9 @@ E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryEntity.swift; sourceTree = ""; }; E07233C9282B82BF00AF3E16 /* Category.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Category.json; sourceTree = ""; }; + E07233D0282BCB5E00AF3E16 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + E07233D3282BCBBC00AF3E16 /* Inject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inject.swift; sourceTree = ""; }; + E07233D5282BCC8100AF3E16 /* ImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -297,7 +303,10 @@ E072339A282B7DB900AF3E16 /* Common */ = { isa = PBXGroup; children = ( + E07233D2282BCBA900AF3E16 /* PropertyWrapper */, E072339B282B7DB900AF3E16 /* Log.swift */, + E07233D0282BCB5E00AF3E16 /* Container.swift */, + E07233D5282BCC8100AF3E16 /* ImageManager.swift */, ); path = Common; sourceTree = ""; @@ -333,6 +342,14 @@ path = Base; sourceTree = ""; }; + E07233D2282BCBA900AF3E16 /* PropertyWrapper */ = { + isa = PBXGroup; + children = ( + E07233D3282BCBBC00AF3E16 /* Inject.swift */, + ); + path = PropertyWrapper; + sourceTree = ""; + }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { isa = PBXGroup; children = ( @@ -562,9 +579,11 @@ E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */, E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */, E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */, + E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */, E07233BB282B7DB900AF3E16 /* Log.swift in Sources */, E07233B1282B7DB900AF3E16 /* OrderTableViewDelegate.swift in Sources */, E07233B7282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, + E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */, E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */, E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */, @@ -575,6 +594,7 @@ E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */, E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, + E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */, E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */, E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */, E07233B4282B7DB900AF3E16 /* CategoryTableViewCell.swift in Sources */, diff --git a/Starbucks/Starbucks/Resource/Category.json b/Starbucks/Starbucks/Resource/Category.json index 4a4e12b..19ee2db 100644 --- a/Starbucks/Starbucks/Resource/Category.json +++ b/Starbucks/Starbucks/Resource/Category.json @@ -4,63 +4,63 @@ "groupId": "W0000171", "title": "콜드브루", "subTitle": "Cold Brew", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2021/04/[9200000000038]_20210430113202458.jpg", "category": "BEVERAGE" }, { "groupId": "W0000003", "title": "에스프레소", "subTitle": "Espresso", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2021/04/[20]_20210415144112678.jpg", "category": "BEVERAGE" }, { "groupId": "W0000004", "title": "프라프치노", "subTitle": "Frappuccino", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2021/04/[9200000002760]_20210415133558068.jpg", "category": "BEVERAGE" }, { - "groupId": "W0000171", + "groupId": "W0000013", "title": "브레드", "subTitle": "Cold Brew", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000004027]_20220407090004058.jpg", "category": "FOOD" }, { - "groupId": "W0000003", + "groupId": "W0000032", "title": "케이크", "subTitle": "Espresso", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000004034]_20220406125047195.jpg", "category": "FOOD" }, { - "groupId": "W0000004", + "groupId": "W0000033", "title": "샌드위치", "subTitle": "Frappuccino", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000004005]_20220420080006656.jpg", "category": "FOOD" }, { - "groupId": "W0000171", + "groupId": "W0000030", "title": "머그", "subTitle": "Cold Brew", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[11132362]_20220406154731279.jpg", "category": "PRODUCT" }, { - "groupId": "W0000003", + "groupId": "W0000164", "title": "글라스", "subTitle": "Espresso", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000003600]_20220413140543290.jpg", "category": "PRODUCT" }, { - "groupId": "W0000004", + "groupId": "W0000031", "title": "텀플러", "subTitle": "Frappuccino", - "imagePath": "", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[11132329]_20220406144652838.jpg", "category": "PRODUCT" } ] diff --git a/Starbucks/Starbucks/Sources/Common/Container.swift b/Starbucks/Starbucks/Sources/Common/Container.swift new file mode 100644 index 0000000..38681df --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/Container.swift @@ -0,0 +1,18 @@ +// +// Container.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import Foundation + +class Container { + static var shared = Container() + + private init() { } + + lazy var starbucksRepository: StarbucksRepository = StarbucksRepositoryImpl() + + lazy var imageManager = ImageManager() +} diff --git a/Starbucks/Starbucks/Sources/Common/ImageManager.swift b/Starbucks/Starbucks/Sources/Common/ImageManager.swift new file mode 100644 index 0000000..de7b084 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/ImageManager.swift @@ -0,0 +1,52 @@ +// +// ImageManager.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import Foundation +import RxSwift +import UIKit + +class ImageManager { + private let imageCache = NSCache() + + func loadImage(url: URL) -> Single { + Single.create { observer in + let imageName = url.lastPathComponent + + if let cacheImage = self.imageCache.object(forKey: imageName as NSString) { + observer(.success(cacheImage)) + return Disposables.create { } + } + + guard let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { + return Disposables.create { } + } + + let destination = cachesDirectory.appendingPathComponent(imageName) + + if FileManager.default.fileExists(atPath: destination.path), + let image = UIImage(contentsOfFile: destination.path) { + self.imageCache.setObject(image, forKey: imageName as NSString) + observer(.success(image)) + return Disposables.create { } + } + + let task = URLSession.shared.downloadTask(with: url) { url, _, _ in + guard let url = url else { return } + try? FileManager.default.copyItem(at: url, to: destination) + + guard let image = UIImage(contentsOfFile: destination.path) else { + return + } + self.imageCache.setObject(image, forKey: imageName as NSString) + observer(.success(image)) + } + task.resume() + + return Disposables.create { } + } + } +} diff --git a/Starbucks/Starbucks/Sources/Common/PropertyWrapper/Inject.swift b/Starbucks/Starbucks/Sources/Common/PropertyWrapper/Inject.swift new file mode 100644 index 0000000..363110c --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/PropertyWrapper/Inject.swift @@ -0,0 +1,19 @@ +// +// Inject.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import Foundation + +@propertyWrapper +struct Inject { + + let wrappedValue: T + + init(_ keyPath: KeyPath) { + let container = Container.shared + wrappedValue = container[keyPath: keyPath] + } +} diff --git a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift index 93ad712..9bcf939 100644 --- a/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift @@ -59,6 +59,6 @@ extension Category { let groupId: String let title: String let subTitle: String - let imagePath: String + let imagePath: URL } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 7b4d7f7..ba831e1 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -32,8 +32,8 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta let loadEvent = PublishRelay() func state() -> HomeViewModelState { self } - - private var starbucksRepository = StarbucksRepositoryImpl() + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift index cee73ff..e966c58 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift @@ -5,17 +5,21 @@ // Created by 김상혁 on 2022/05/10. // +import RxSwift import UIKit class CategoryTableViewCell: UITableViewCell { static var identifier: String { "\(self)" } + private let disposeBag = DisposeBag() private let menuImageView: UIImageView = { - let image = UIImage(named: "mockImage.png") - let imageView = UIImageView(image: image) + let imageView = UIImageView() + imageView.backgroundColor = .gray imageView.layer.cornerRadius = 40 imageView.clipsToBounds = true + imageView.layer.borderColor = UIColor.gray.cgColor + imageView.layer.borderWidth = 0.5 return imageView }() @@ -40,6 +44,8 @@ class CategoryTableViewCell: UITableViewCell { return label }() + @Inject(\.imageManager) private var imageManager: ImageManager + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) layout() @@ -76,4 +82,14 @@ class CategoryTableViewCell: UITableViewCell { func setSubName(text: String) { subNameLabel.text = text } + + func setThumbnail(url: URL) { + imageManager.loadImage(url: url).asObservable() + .withUnretained(self) + .observe(on: MainScheduler.asyncInstance) + .bind(onNext: { cell, image in + cell.menuImageView.image = image + }) + .disposed(by: disposeBag) + } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift index 2d83ed7..d70ce7e 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift @@ -6,8 +6,8 @@ // import UIKit -import RxRelay import RxSwift +import RxRelay class OrderTableViewDataSource: NSObject, UITableViewDataSource { @@ -29,6 +29,7 @@ class OrderTableViewDataSource: NSObject, UITableViewDataSource { } cell.setMenuName(text: menus[indexPath.row].title) cell.setSubName(text: menus[indexPath.row].subTitle) + cell.setThumbnail(url: menus[indexPath.row].imagePath) return cell } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 8aa6776..2177754 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -40,9 +40,9 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB let loadedCategory = PublishRelay<[Category.Group]>() let selectedCategory = PublishRelay() - let selectedMenu = PublishRelay() + let selectedMenu = PublishRelay() - private var starbucksRepository = StarbucksRepositoryImpl() + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() private var categoryMenu = Category.GroupType.allCases.reduce(into: [Category.GroupType: [Category.Group]]()) { @@ -51,7 +51,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB init() { - //MARK: Repository에서 카테고리 Json 파일을 로드 + // MARK: Repository에서 카테고리 Json 파일을 로드 let requestCategory = action().loadCategory .withUnretained(self) .flatMapLatest { model, _ in @@ -65,7 +65,8 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB .map { groups in groups.reduce(into: self.categoryMenu) { category, group in category[group.category]?.append(group) - } } //값이 있으면 Array -> dic로 변환 + } + } //값이 있으면 Array -> dic로 변환 .withUnretained(self) //weak self를 생략하기 위한 오퍼레이터 .do { model, groups in model.categoryMenu = groups } //모델의 dic에 값을 넣어주고 .map { _ in .beverage } //처음 보여질 테이블은 beverage이므로 값을 넘겨줌 From a27703f4bbb6b80946e7e9b02a2a53bbad4d19a3 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 11 May 2022 22:57:22 +0900 Subject: [PATCH 29/57] =?UTF-8?q?[STAR-13]=20feature:=20=EC=B6=94=EC=B2=9C?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20UI=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 28 ++++++ .../Sources/Model/StarbucksEntity.swift | 7 +- Starbucks/Starbucks/Sources/Present/.DS_Store | Bin 6148 -> 6148 bytes .../Present/Home/HomeViewController.swift | 28 +++++- .../Sources/Present/Home/HomeViewModel.swift | 8 ++ .../SuggestionMenuCellView.swift | 53 +++++++++++ .../SuggestionMenuDataSource.swift | 29 ++++++ .../SuggestionMenu/SuggestionMenuView.swift | 87 ++++++++++++++++++ 8 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuCellView.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuDataSource.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuView.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index 7765686..c2a5c50 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -44,6 +44,9 @@ E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D0282BCB5E00AF3E16 /* Container.swift */; }; E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D3282BCBBC00AF3E16 /* Inject.swift */; }; E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D5282BCC8100AF3E16 /* ImageManager.swift */; }; + E07233D9282BD60200AF3E16 /* SuggestionMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D8282BD60200AF3E16 /* SuggestionMenuView.swift */; }; + E07233DB282BD71900AF3E16 /* SuggestionMenuCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DA282BD71900AF3E16 /* SuggestionMenuCellView.swift */; }; + E07233DE282BDDB200AF3E16 /* SuggestionMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DD282BDDB200AF3E16 /* SuggestionMenuDataSource.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -100,6 +103,9 @@ E07233D0282BCB5E00AF3E16 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; E07233D3282BCBBC00AF3E16 /* Inject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inject.swift; sourceTree = ""; }; E07233D5282BCC8100AF3E16 /* ImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = ""; }; + E07233D8282BD60200AF3E16 /* SuggestionMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionMenuView.swift; sourceTree = ""; }; + E07233DA282BD71900AF3E16 /* SuggestionMenuCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionMenuCellView.swift; sourceTree = ""; }; + E07233DD282BDDB200AF3E16 /* SuggestionMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionMenuDataSource.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -223,6 +229,7 @@ E0723380282B7DB900AF3E16 /* Home */ = { isa = PBXGroup; children = ( + E07233D7282BD5B800AF3E16 /* View */, E0723381282B7DB900AF3E16 /* HomeViewModel.swift */, E0723382282B7DB900AF3E16 /* HomeViewController.swift */, ); @@ -350,6 +357,24 @@ path = PropertyWrapper; sourceTree = ""; }; + E07233D7282BD5B800AF3E16 /* View */ = { + isa = PBXGroup; + children = ( + E07233DC282BDD9C00AF3E16 /* SuggestionMenu */, + ); + path = View; + sourceTree = ""; + }; + E07233DC282BDD9C00AF3E16 /* SuggestionMenu */ = { + isa = PBXGroup; + children = ( + E07233D8282BD60200AF3E16 /* SuggestionMenuView.swift */, + E07233DA282BD71900AF3E16 /* SuggestionMenuCellView.swift */, + E07233DD282BDDB200AF3E16 /* SuggestionMenuDataSource.swift */, + ); + path = SuggestionMenu; + sourceTree = ""; + }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { isa = PBXGroup; children = ( @@ -577,6 +602,7 @@ E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */, E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */, + E07233DB282BD71900AF3E16 /* SuggestionMenuCellView.swift in Sources */, E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */, E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */, E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */, @@ -585,12 +611,14 @@ E07233B7282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */, E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */, + E07233DE282BDDB200AF3E16 /* SuggestionMenuDataSource.swift in Sources */, E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */, E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */, E07233AC282B7DB900AF3E16 /* RootWindow.swift in Sources */, E07233C4282B7DB900AF3E16 /* Response.swift in Sources */, E07233B5282B7DB900AF3E16 /* OrderViewModel.swift in Sources */, + E07233D9282BD60200AF3E16 /* SuggestionMenuView.swift in Sources */, E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */, E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift index 10f695d..da32c57 100644 --- a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -40,7 +40,6 @@ extension StarbucksEntity { } extension StarbucksEntity { - struct HomeEvent: Decodable { let list: [Event] } @@ -57,3 +56,9 @@ extension StarbucksEntity { } } } + +extension StarbucksEntity { + struct ProductDetail: Decodable { + + } +} diff --git a/Starbucks/Starbucks/Sources/Present/.DS_Store b/Starbucks/Starbucks/Sources/Present/.DS_Store index 65fce4bedea25d6e69703aa6ceae2122d27fcd59..b2f4a6b40de163876807cbcf627efd45c9b57bf0 100644 GIT binary patch delta 67 zcmZoMXfc=|#>B`mu~2NHo+2a5#DLw5ER%Vdv^V=Q?_$~ffmw`cGdl-A2T;l8hs@uZ VC-aL~axee^BLf4|<^Yi`%m7wU5I_I` delta 344 zcmZoMXfc=|#>B)qu~2NHo+2ar#DLw4A22d9vQOq=)UIb`@MkDuNMT52DDuq7Pfp6o zPhwzT5CG!!K&K1|*e9<;4Y1l{=COax#lc3=FO@GBLBTvaxfpb8vIS2501# z2bUz4lomTB7Da=2A^G_^NicR|QdnkcdAxu~y>otENn&PRY7tmRW=bkhO-y)ZUP^ws zQ+{b)N-U}m*0&Cd&(6us z%kKg^nt_oKLNo9}X&BWFWUw-b!hK&BT$GoSpO+34X54JTxR+%!I|n}pFx)m@Wc Int { + 10 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SuggestionMenuCellView.identifier, for: indexPath) as? SuggestionMenuCellView else { + return UICollectionViewCell() + } + return cell + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuView.swift new file mode 100644 index 0000000..1e46a65 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuView.swift @@ -0,0 +1,87 @@ +// +// SuggestionMenuView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import UIKit + +class SuggestionMenuView: UIView { + enum Constants { + static let cellSize = CGSize(width: 130, height: 160) + } + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "싱하 님을 위한 추천 메뉴" + label.textAlignment = .left + label.font = .systemFont(ofSize: 24, weight: .bold) + label.textColor = .black + return label + }() + + private let menuCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .horizontal + flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) + flowLayout.minimumLineSpacing = 15 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.isScrollEnabled = true + collectionView.register(SuggestionMenuCellView.self, forCellWithReuseIdentifier: SuggestionMenuCellView.identifier) + collectionView.showsHorizontalScrollIndicator = false + return collectionView + }() + + private var dataSource: SuggestionMenuDataSource? + + override init(frame: CGRect) { + super.init(frame: frame) + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func attribute() { + menuCollectionView.delegate = self + menuCollectionView.reloadData() + } + + private func layout() { + addSubview(titleLabel) + addSubview(menuCollectionView) + + snp.makeConstraints { + $0.bottom.equalTo(menuCollectionView) + } + + titleLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20) + $0.top.trailing.equalToSuperview() + } + + menuCollectionView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(20) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(Constants.cellSize.height) + } + } + + func updateDataSource(products: [StarbucksEntity.ProductDetail]) { + self.dataSource = SuggestionMenuDataSource(products: products) + DispatchQueue.main.async { + self.menuCollectionView.dataSource = self.dataSource + self.menuCollectionView.reloadData() + } + } +} + +extension SuggestionMenuView: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + Constants.cellSize + } +} From 896ffa8485a2cad459cd36a59c54888aed642bf2 Mon Sep 17 00:00:00 2001 From: shingha Date: Thu, 12 May 2022 01:24:47 +0900 Subject: [PATCH 30/57] =?UTF-8?q?[STAR-13]=20feature:=20=EC=A0=9C=ED=92=88?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=8C=8C=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 30 +++++++++---------- .../Sources/API/StarbucksTarget.swift | 11 +++++-- .../Sources/Model/StarbucksEntity.swift | 8 +++++ .../Present/Home/HomeViewController.swift | 14 ++++----- .../Sources/Present/Home/HomeViewModel.swift | 18 +++++++---- .../RecommandMenuCellView.swift} | 10 +++++-- .../RecommandMenuDataSource.swift} | 14 +++++---- .../RecommandMenuView.swift} | 12 ++++---- .../Starbucks/StarbucksRepository.swift | 1 + .../Starbucks/StarbucksRepositoryImpl.swift | 6 ++++ 10 files changed, 79 insertions(+), 45 deletions(-) rename Starbucks/Starbucks/Sources/Present/Home/View/{SuggestionMenu/SuggestionMenuCellView.swift => Recommand/RecommandMenuCellView.swift} (81%) rename Starbucks/Starbucks/Sources/Present/Home/View/{SuggestionMenu/SuggestionMenuDataSource.swift => Recommand/RecommandMenuDataSource.swift} (57%) rename Starbucks/Starbucks/Sources/Present/Home/View/{SuggestionMenu/SuggestionMenuView.swift => Recommand/RecommandMenuView.swift} (84%) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index c2a5c50..f24cc71 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -44,9 +44,9 @@ E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D0282BCB5E00AF3E16 /* Container.swift */; }; E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D3282BCBBC00AF3E16 /* Inject.swift */; }; E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D5282BCC8100AF3E16 /* ImageManager.swift */; }; - E07233D9282BD60200AF3E16 /* SuggestionMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D8282BD60200AF3E16 /* SuggestionMenuView.swift */; }; - E07233DB282BD71900AF3E16 /* SuggestionMenuCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DA282BD71900AF3E16 /* SuggestionMenuCellView.swift */; }; - E07233DE282BDDB200AF3E16 /* SuggestionMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DD282BDDB200AF3E16 /* SuggestionMenuDataSource.swift */; }; + E07233D9282BD60200AF3E16 /* RecommandMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D8282BD60200AF3E16 /* RecommandMenuView.swift */; }; + E07233DB282BD71900AF3E16 /* RecommandMenuCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */; }; + E07233DE282BDDB200AF3E16 /* RecommandMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -103,9 +103,9 @@ E07233D0282BCB5E00AF3E16 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; E07233D3282BCBBC00AF3E16 /* Inject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inject.swift; sourceTree = ""; }; E07233D5282BCC8100AF3E16 /* ImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = ""; }; - E07233D8282BD60200AF3E16 /* SuggestionMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionMenuView.swift; sourceTree = ""; }; - E07233DA282BD71900AF3E16 /* SuggestionMenuCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionMenuCellView.swift; sourceTree = ""; }; - E07233DD282BDDB200AF3E16 /* SuggestionMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionMenuDataSource.swift; sourceTree = ""; }; + E07233D8282BD60200AF3E16 /* RecommandMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuView.swift; sourceTree = ""; }; + E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuCellView.swift; sourceTree = ""; }; + E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuDataSource.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -360,19 +360,19 @@ E07233D7282BD5B800AF3E16 /* View */ = { isa = PBXGroup; children = ( - E07233DC282BDD9C00AF3E16 /* SuggestionMenu */, + E07233DC282BDD9C00AF3E16 /* Recommand */, ); path = View; sourceTree = ""; }; - E07233DC282BDD9C00AF3E16 /* SuggestionMenu */ = { + E07233DC282BDD9C00AF3E16 /* Recommand */ = { isa = PBXGroup; children = ( - E07233D8282BD60200AF3E16 /* SuggestionMenuView.swift */, - E07233DA282BD71900AF3E16 /* SuggestionMenuCellView.swift */, - E07233DD282BDDB200AF3E16 /* SuggestionMenuDataSource.swift */, + E07233D8282BD60200AF3E16 /* RecommandMenuView.swift */, + E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */, + E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */, ); - path = SuggestionMenu; + path = Recommand; sourceTree = ""; }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { @@ -602,7 +602,7 @@ E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */, E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */, - E07233DB282BD71900AF3E16 /* SuggestionMenuCellView.swift in Sources */, + E07233DB282BD71900AF3E16 /* RecommandMenuCellView.swift in Sources */, E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */, E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */, E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */, @@ -611,14 +611,14 @@ E07233B7282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */, E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */, - E07233DE282BDDB200AF3E16 /* SuggestionMenuDataSource.swift in Sources */, + E07233DE282BDDB200AF3E16 /* RecommandMenuDataSource.swift in Sources */, E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */, E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */, E07233AC282B7DB900AF3E16 /* RootWindow.swift in Sources */, E07233C4282B7DB900AF3E16 /* Response.swift in Sources */, E07233B5282B7DB900AF3E16 /* OrderViewModel.swift in Sources */, - E07233D9282BD60200AF3E16 /* SuggestionMenuView.swift in Sources */, + E07233D9282BD60200AF3E16 /* RecommandMenuView.swift in Sources */, E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */, E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift index f2ec689..1c74dfc 100644 --- a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -10,6 +10,7 @@ import Foundation enum StarbucksTarget: BaseTarget { case requestHome case requestEvent + case requestDetail(_ id: String) } extension StarbucksTarget { @@ -20,12 +21,14 @@ extension StarbucksTarget { return URL(string: "https://api.codesquad.kr/starbuckst") case .requestEvent: return URL(string: "https://www.starbucks.co.kr") + case .requestDetail: + return URL(string: "https://www.starbucks.co.kr/menu/productViewAjax.do") } } var path: String? { switch self { - case .requestHome: + case .requestHome, .requestDetail: return nil case .requestEvent: return "/whats_new/getIngList.do" @@ -38,6 +41,8 @@ extension StarbucksTarget { return nil case .requestEvent: return ["MENU_CD": "all"] + case .requestDetail(let id): + return ["product_cd": id] } } @@ -45,7 +50,7 @@ extension StarbucksTarget { switch self { case .requestHome: return .get - case .requestEvent: + case .requestEvent, .requestDetail: return .post } } @@ -54,7 +59,7 @@ extension StarbucksTarget { switch self { case .requestHome: return .json - case .requestEvent: + case .requestEvent, .requestDetail: return .urlencode } } diff --git a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift index da32c57..5939e49 100644 --- a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -59,6 +59,14 @@ extension StarbucksEntity { extension StarbucksEntity { struct ProductDetail: Decodable { + let view: ProductDatailData? + } + + struct ProductDatailData: Decodable { + let productName: String + enum CodingKeys: String, CodingKey { + case productName = "product_NM" + } } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 7c0617d..5855fde 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -24,9 +24,9 @@ class HomeViewController: UIViewController { return stackView }() - private let suggestionMenuView: SuggestionMenuView = { - let suggestionView = SuggestionMenuView(frame: .zero) - return suggestionView + private let recommandMenuView: RecommandMenuView = { + let recommandMenuView = RecommandMenuView(frame: .zero) + return recommandMenuView }() private let viewModel: HomeViewModelProtocol @@ -53,10 +53,10 @@ class HomeViewController: UIViewController { .bind(to: viewModel.action().loadEvent) .disposed(by: disposeBag) - rx.viewDidLoad + viewModel.state().loadedRecommandMenu .withUnretained(self) - .bind(onNext: { viewController, _ in - viewController.suggestionMenuView.updateDataSource(products: []) + .bind(onNext: { vc, details in + vc.recommandMenuView.updateDataSource(details) }) .disposed(by: disposeBag) } @@ -64,7 +64,7 @@ class HomeViewController: UIViewController { private func layout() { view.addSubview(scrollView) scrollView.addSubview(contentStackView) - contentStackView.addArrangedSubview(suggestionMenuView) + contentStackView.addArrangedSubview(recommandMenuView) scrollView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index c618973..015ab02 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -15,7 +15,7 @@ protocol HomeViewModelAction { } protocol HomeViewModelState { - var loadEvent: PublishRelay { get } + var loadedRecommandMenu: PublishRelay<[StarbucksEntity.ProductDatailData]> { get } } protocol HomeViewModelBinding { @@ -32,6 +32,8 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta let loadEvent = PublishRelay() func state() -> HomeViewModelState { self } + + let loadedRecommandMenu = PublishRelay<[StarbucksEntity.ProductDatailData]>() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository @@ -52,11 +54,15 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta .disposed(by: disposeBag) requestHome - .compactMap { $0.value } - .compactMap { $0.map { $0.yourRecommand } } - .bind(onNext: { - print($0) - }) + .compactMap { $0.value?.yourRecommand.products } + .flatMapLatest { ids in + Observable.zip( ids.map { id in + self.starbucksRepository.requestDetail(id).asObservable() + .compactMap { $0.value } + }) + } + .map { $0.compactMap { $0.view } } + .bind(to: loadedRecommandMenu) .disposed(by: disposeBag) let requestEvent = action().loadEvent diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuCellView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift similarity index 81% rename from Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuCellView.swift rename to Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift index a4fa520..068da76 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuCellView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift @@ -7,15 +7,15 @@ import UIKit -class SuggestionMenuCellView: UICollectionViewCell { - static let identifier = "SuggestionMenuCellView" +class RecommandMenuCellView: UICollectionViewCell { + static let identifier = "RecommandMenuCellView" private let thumbnailView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "mockImage") imageView.backgroundColor = .brown imageView.clipsToBounds = true - imageView.layer.cornerRadius = SuggestionMenuView.Constants.cellSize.width / 2 + imageView.layer.cornerRadius = RecommandMenuView.Constants.cellSize.width / 2 return imageView }() @@ -50,4 +50,8 @@ class SuggestionMenuCellView: UICollectionViewCell { $0.leading.trailing.bottom.equalToSuperview() } } + + func setName(_ name: String) { + nameLabel.text = name + } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift similarity index 57% rename from Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuDataSource.swift rename to Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift index 87a63ed..b5ca2bf 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift @@ -8,22 +8,26 @@ import Foundation import UIKit -class SuggestionMenuDataSource: NSObject, UICollectionViewDataSource { +class RecommandMenuDataSource: NSObject, UICollectionViewDataSource { - private let products: [StarbucksEntity.ProductDetail] + private let products: [StarbucksEntity.ProductDatailData] - init(products: [StarbucksEntity.ProductDetail]) { + init(products: [StarbucksEntity.ProductDatailData]) { self.products = products } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - 10 + products.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SuggestionMenuCellView.identifier, for: indexPath) as? SuggestionMenuCellView else { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecommandMenuCellView.identifier, for: indexPath) as? RecommandMenuCellView else { return UICollectionViewCell() } + + let product = products[indexPath.item] + + cell.setName(product.productName) return cell } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift similarity index 84% rename from Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuView.swift rename to Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift index 1e46a65..1d8ceba 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/SuggestionMenu/SuggestionMenuView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift @@ -7,7 +7,7 @@ import UIKit -class SuggestionMenuView: UIView { +class RecommandMenuView: UIView { enum Constants { static let cellSize = CGSize(width: 130, height: 160) } @@ -28,12 +28,12 @@ class SuggestionMenuView: UIView { flowLayout.minimumLineSpacing = 15 let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) collectionView.isScrollEnabled = true - collectionView.register(SuggestionMenuCellView.self, forCellWithReuseIdentifier: SuggestionMenuCellView.identifier) + collectionView.register(RecommandMenuCellView.self, forCellWithReuseIdentifier: RecommandMenuCellView.identifier) collectionView.showsHorizontalScrollIndicator = false return collectionView }() - private var dataSource: SuggestionMenuDataSource? + private var dataSource: RecommandMenuDataSource? override init(frame: CGRect) { super.init(frame: frame) @@ -71,8 +71,8 @@ class SuggestionMenuView: UIView { } } - func updateDataSource(products: [StarbucksEntity.ProductDetail]) { - self.dataSource = SuggestionMenuDataSource(products: products) + func updateDataSource(_ products: [StarbucksEntity.ProductDatailData]) { + self.dataSource = RecommandMenuDataSource(products: products) DispatchQueue.main.async { self.menuCollectionView.dataSource = self.dataSource self.menuCollectionView.reloadData() @@ -80,7 +80,7 @@ class SuggestionMenuView: UIView { } } -extension SuggestionMenuView: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { +extension RecommandMenuView: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { Constants.cellSize } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift index 083dd35..b05cd6c 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -11,5 +11,6 @@ import RxSwift protocol StarbucksRepository { func requestHome() -> Single> func requestEvent() -> Single> + func requestDetail(_ id: String) -> Single> func requestCategory() -> Single> } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift index 0e8d5b9..8126110 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -21,6 +21,12 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo .map(StarbucksEntity.HomeEvent.self) } + func requestDetail(_ id: String) -> Single> { + provider + .request(.requestDetail(id)) + .map(StarbucksEntity.ProductDetail.self) + } + func requestCategory() -> Single> { Single.create { observer in guard let url = Bundle.main.url(forResource: "Category", withExtension: "json"), From 018f321ee5b6a2dcbcce874655f9794d9022935e Mon Sep 17 00:00:00 2001 From: shingha Date: Thu, 12 May 2022 11:17:49 +0900 Subject: [PATCH 31/57] =?UTF-8?q?[STAR-13]=20feature:=20=EC=B6=94=EC=B2=9C?= =?UTF-8?q?=20=EC=A0=9C=ED=92=88=20=EB=B7=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/API/StarbucksTarget.swift | 21 +++++++++++------- .../Sources/Model/StarbucksEntity.swift | 22 +++++++++++++++++-- .../Present/Home/HomeViewController.swift | 7 ++++++ .../Sources/Present/Home/HomeViewModel.swift | 21 ++++++++++++++++-- .../Recommand/RecommandMenuCellView.swift | 21 +++++++++++++++++- .../Recommand/RecommandMenuDataSource.swift | 14 +++++++++--- .../View/Recommand/RecommandMenuView.swift | 17 +++++++++----- .../OrderCategory/CategoryTableViewCell.swift | 2 +- .../Starbucks/StarbucksRepository.swift | 1 + .../Starbucks/StarbucksRepositoryImpl.swift | 8 ++++++- 10 files changed, 111 insertions(+), 23 deletions(-) diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift index 1c74dfc..b9e0fb9 100644 --- a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -10,7 +10,8 @@ import Foundation enum StarbucksTarget: BaseTarget { case requestHome case requestEvent - case requestDetail(_ id: String) + case requestProductInfo(_ id: String) + case requestProductImage(_ id: String) } extension StarbucksTarget { @@ -19,19 +20,21 @@ extension StarbucksTarget { switch self { case .requestHome: return URL(string: "https://api.codesquad.kr/starbuckst") - case .requestEvent: + case .requestEvent, .requestProductInfo, .requestProductImage: return URL(string: "https://www.starbucks.co.kr") - case .requestDetail: - return URL(string: "https://www.starbucks.co.kr/menu/productViewAjax.do") } } var path: String? { switch self { - case .requestHome, .requestDetail: + case .requestHome: return nil case .requestEvent: return "/whats_new/getIngList.do" + case .requestProductInfo: + return "/menu/productViewAjax.do" + case .requestProductImage: + return "/menu/productFileAjax.do" } } @@ -41,8 +44,10 @@ extension StarbucksTarget { return nil case .requestEvent: return ["MENU_CD": "all"] - case .requestDetail(let id): + case .requestProductInfo(let id): return ["product_cd": id] + case .requestProductImage(let id): + return ["PRODUCT_CD": id] } } @@ -50,7 +55,7 @@ extension StarbucksTarget { switch self { case .requestHome: return .get - case .requestEvent, .requestDetail: + case .requestEvent, .requestProductInfo, .requestProductImage: return .post } } @@ -59,7 +64,7 @@ extension StarbucksTarget { switch self { case .requestHome: return .json - case .requestEvent, .requestDetail: + case .requestEvent, .requestProductInfo, .requestProductImage: return .urlencode } } diff --git a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift index 5939e49..c039d78 100644 --- a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -59,14 +59,32 @@ extension StarbucksEntity { extension StarbucksEntity { struct ProductDetail: Decodable { - let view: ProductDatailData? + let view: ProductInfo? } - struct ProductDatailData: Decodable { + struct ProductInfo: Decodable { let productName: String enum CodingKeys: String, CodingKey { case productName = "product_NM" } } + + struct ProductImage: Decodable { + let file: [ProductImageInfo]? + } + + struct ProductImageInfo: Decodable { + let filePath: String + let imageUploadPath: URL + + enum CodingKeys: String, CodingKey { + case filePath = "file_PATH" + case imageUploadPath = "img_UPLOAD_PATH" + } + + var imageUrl: URL { + imageUploadPath.appendingPathComponent(filePath) + } + } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 5855fde..9bc99b4 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -59,6 +59,13 @@ class HomeViewController: UIViewController { vc.recommandMenuView.updateDataSource(details) }) .disposed(by: disposeBag) + + viewModel.state().loadedRecommandImage + .withUnretained(self) + .bind(onNext: { vc, images in + vc.recommandMenuView.updateDataSource(images) + }) + .disposed(by: disposeBag) } private func layout() { diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 015ab02..4c372fc 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -15,7 +15,8 @@ protocol HomeViewModelAction { } protocol HomeViewModelState { - var loadedRecommandMenu: PublishRelay<[StarbucksEntity.ProductDatailData]> { get } + var loadedRecommandMenu: PublishRelay<[StarbucksEntity.ProductInfo]> { get } + var loadedRecommandImage: PublishRelay<[[StarbucksEntity.ProductImageInfo]]> { get } } protocol HomeViewModelBinding { @@ -33,7 +34,8 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta func state() -> HomeViewModelState { self } - let loadedRecommandMenu = PublishRelay<[StarbucksEntity.ProductDatailData]>() + let loadedRecommandMenu = PublishRelay<[StarbucksEntity.ProductInfo]>() + let loadedRecommandImage = PublishRelay<[[StarbucksEntity.ProductImageInfo]]>() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository @@ -65,6 +67,21 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta .bind(to: loadedRecommandMenu) .disposed(by: disposeBag) + requestHome + .compactMap { $0.value?.yourRecommand.products } + .do { print($0) } + .flatMapLatest { ids in + Observable.zip( ids.map { id in + self.starbucksRepository.requestDetailImage(id).asObservable() + .compactMap { $0.value } + }) + } + .map { $0.compactMap { $0.file }.filter { !$0.isEmpty } } + .bind(to: loadedRecommandImage) + .disposed(by: disposeBag) + + + let requestEvent = action().loadEvent .withUnretained(self) .flatMapLatest { model, _ in diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift index 068da76..d19a4d3 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift @@ -5,6 +5,7 @@ // Created by seongha shin on 2022/05/11. // +import RxSwift import UIKit class RecommandMenuCellView: UICollectionViewCell { @@ -21,12 +22,15 @@ class RecommandMenuCellView: UICollectionViewCell { private let nameLabel: UILabel = { let label = UILabel() - label.text = "클래식 스콘" label.font = .systemFont(ofSize: 15) label.textAlignment = .center + label.numberOfLines = 2 return label }() + @Inject(\.imageManager) private var imageManager: ImageManager + private let disposeBag = DisposeBag() + override init(frame: CGRect) { super.init(frame: frame) layout() @@ -47,6 +51,7 @@ class RecommandMenuCellView: UICollectionViewCell { } nameLabel.snp.makeConstraints { + $0.top.equalTo(thumbnailView.snp.bottom) $0.leading.trailing.bottom.equalToSuperview() } } @@ -54,4 +59,18 @@ class RecommandMenuCellView: UICollectionViewCell { func setName(_ name: String) { nameLabel.text = name } + + func setThumbnail(_ imageUrl: URL?) { + guard let url = imageUrl else { + return + } + + imageManager.loadImage(url: url).asObservable() + .withUnretained(self) + .observe(on: MainScheduler.asyncInstance) + .bind(onNext: { cell, image in + cell.thumbnailView.image = image + }) + .disposed(by: disposeBag) + } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift index b5ca2bf..7cb1e9c 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift @@ -10,11 +10,16 @@ import UIKit class RecommandMenuDataSource: NSObject, UICollectionViewDataSource { - private let products: [StarbucksEntity.ProductDatailData] + private var products: [StarbucksEntity.ProductInfo] = [] + private var productimages: [[StarbucksEntity.ProductImageInfo]] = [] - init(products: [StarbucksEntity.ProductDatailData]) { + func updateProducts(_ products: [StarbucksEntity.ProductInfo]) { self.products = products } + + func updateProductImages(_ images: [[StarbucksEntity.ProductImageInfo]]) { + self.productimages = images + } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { products.count @@ -25,9 +30,12 @@ class RecommandMenuDataSource: NSObject, UICollectionViewDataSource { return UICollectionViewCell() } - let product = products[indexPath.item] + let index = indexPath.item + let product = products[index] + let image = index >= productimages.count ? nil : productimages[index].first cell.setName(product.productName) + cell.setThumbnail(image?.imageUrl) return cell } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift index 1d8ceba..ced84d9 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift @@ -9,7 +9,7 @@ import UIKit class RecommandMenuView: UIView { enum Constants { - static let cellSize = CGSize(width: 130, height: 160) + static let cellSize = CGSize(width: 130, height: 180) } private let titleLabel: UILabel = { @@ -33,7 +33,7 @@ class RecommandMenuView: UIView { return collectionView }() - private var dataSource: RecommandMenuDataSource? + private let dataSource = RecommandMenuDataSource() override init(frame: CGRect) { super.init(frame: frame) @@ -47,6 +47,7 @@ class RecommandMenuView: UIView { } private func attribute() { + menuCollectionView.dataSource = dataSource menuCollectionView.delegate = self menuCollectionView.reloadData() } @@ -71,10 +72,16 @@ class RecommandMenuView: UIView { } } - func updateDataSource(_ products: [StarbucksEntity.ProductDatailData]) { - self.dataSource = RecommandMenuDataSource(products: products) + func updateDataSource(_ products: [StarbucksEntity.ProductInfo]) { + self.dataSource.updateProducts(products) + DispatchQueue.main.async { + self.menuCollectionView.reloadData() + } + } + + func updateDataSource( _ images: [[StarbucksEntity.ProductImageInfo]]) { + self.dataSource.updateProductImages(images) DispatchQueue.main.async { - self.menuCollectionView.dataSource = self.dataSource self.menuCollectionView.reloadData() } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift index e966c58..8b1f396 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift @@ -11,7 +11,6 @@ import UIKit class CategoryTableViewCell: UITableViewCell { static var identifier: String { "\(self)" } - private let disposeBag = DisposeBag() private let menuImageView: UIImageView = { let imageView = UIImageView() @@ -45,6 +44,7 @@ class CategoryTableViewCell: UITableViewCell { }() @Inject(\.imageManager) private var imageManager: ImageManager + private let disposeBag = DisposeBag() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift index b05cd6c..ebdc368 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -12,5 +12,6 @@ protocol StarbucksRepository { func requestHome() -> Single> func requestEvent() -> Single> func requestDetail(_ id: String) -> Single> + func requestDetailImage(_ id: String) -> Single> func requestCategory() -> Single> } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift index 8126110..0500f80 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -23,10 +23,16 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo func requestDetail(_ id: String) -> Single> { provider - .request(.requestDetail(id)) + .request(.requestProductInfo(id)) .map(StarbucksEntity.ProductDetail.self) } + func requestDetailImage(_ id: String) -> Single> { + provider + .request(.requestProductImage(id)) + .map(StarbucksEntity.ProductImage.self) + } + func requestCategory() -> Single> { Single.create { observer in guard let url = Bundle.main.url(forResource: "Category", withExtension: "json"), From cc1a4e149f90851e6e061ec2b8dfb32945dae42e Mon Sep 17 00:00:00 2001 From: shingha Date: Thu, 12 May 2022 19:42:21 +0900 Subject: [PATCH 32/57] =?UTF-8?q?[STAR-13]=20feature:=20=ED=99=88=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84=20-?= =?UTF-8?q?=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5,=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 58 +++++++-- .../Sources/Extension/UIColor+Extension.swift | 2 +- .../Present/Home/HomeViewController.swift | 39 +++--- .../Sources/Present/Home/HomeViewModel.swift | 68 +++-------- .../MainEvent/MainEventViewController.swift | 68 +++++++++++ .../View/MainEvent/MainEventViewModel.swift | 58 +++++++++ .../Recommand/RecommandMenuCellView.swift | 10 +- ...wift => RecommandMenuViewController.swift} | 62 ++++++---- .../Recommand/RecommandMenuViewModel.swift | 74 ++++++++++++ .../Home/View/WhatsNew/WhatsNewCellView.swift | 74 ++++++++++++ .../View/WhatsNew/WhatsNewDataSource.swift | 31 +++++ .../WhatsNew/WhatsNewViewController.swift | 114 ++++++++++++++++++ .../View/WhatsNew/WhatsNewViewModel.swift | 60 +++++++++ .../TabBar/StarbucksViewController.swift | 2 +- 14 files changed, 613 insertions(+), 107 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift rename Starbucks/Starbucks/Sources/Present/Home/View/Recommand/{RecommandMenuView.swift => RecommandMenuViewController.swift} (59%) create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index f24cc71..c1791b2 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -44,9 +44,16 @@ E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D0282BCB5E00AF3E16 /* Container.swift */; }; E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D3282BCBBC00AF3E16 /* Inject.swift */; }; E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D5282BCC8100AF3E16 /* ImageManager.swift */; }; - E07233D9282BD60200AF3E16 /* RecommandMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D8282BD60200AF3E16 /* RecommandMenuView.swift */; }; + E07233D9282BD60200AF3E16 /* RecommandMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233D8282BD60200AF3E16 /* RecommandMenuViewController.swift */; }; E07233DB282BD71900AF3E16 /* RecommandMenuCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */; }; E07233DE282BDDB200AF3E16 /* RecommandMenuDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */; }; + E07233E3282CA71B00AF3E16 /* MainEventViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233E2282CA71B00AF3E16 /* MainEventViewController.swift */; }; + E07233E6282D039600AF3E16 /* WhatsNewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233E5282D039600AF3E16 /* WhatsNewViewController.swift */; }; + E07233E8282D03A000AF3E16 /* WhatsNewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233E7282D03A000AF3E16 /* WhatsNewViewModel.swift */; }; + E07233EA282D075B00AF3E16 /* MainEventViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233E9282D075B00AF3E16 /* MainEventViewModel.swift */; }; + E07233EC282D0BE500AF3E16 /* RecommandMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233EB282D0BE500AF3E16 /* RecommandMenuViewModel.swift */; }; + E07233EE282D0F9300AF3E16 /* WhatsNewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233ED282D0F9300AF3E16 /* WhatsNewDataSource.swift */; }; + E07233F0282D0F9D00AF3E16 /* WhatsNewCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233EF282D0F9D00AF3E16 /* WhatsNewCellView.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -103,9 +110,16 @@ E07233D0282BCB5E00AF3E16 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; E07233D3282BCBBC00AF3E16 /* Inject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inject.swift; sourceTree = ""; }; E07233D5282BCC8100AF3E16 /* ImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = ""; }; - E07233D8282BD60200AF3E16 /* RecommandMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuView.swift; sourceTree = ""; }; + E07233D8282BD60200AF3E16 /* RecommandMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuViewController.swift; sourceTree = ""; }; E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuCellView.swift; sourceTree = ""; }; E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuDataSource.swift; sourceTree = ""; }; + E07233E2282CA71B00AF3E16 /* MainEventViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainEventViewController.swift; sourceTree = ""; }; + E07233E5282D039600AF3E16 /* WhatsNewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewViewController.swift; sourceTree = ""; }; + E07233E7282D03A000AF3E16 /* WhatsNewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewViewModel.swift; sourceTree = ""; }; + E07233E9282D075B00AF3E16 /* MainEventViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainEventViewModel.swift; sourceTree = ""; }; + E07233EB282D0BE500AF3E16 /* RecommandMenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuViewModel.swift; sourceTree = ""; }; + E07233ED282D0F9300AF3E16 /* WhatsNewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewDataSource.swift; sourceTree = ""; }; + E07233EF282D0F9D00AF3E16 /* WhatsNewCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewCellView.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -200,12 +214,12 @@ E072337E282B7DB900AF3E16 /* Sources */ = { isa = PBXGroup; children = ( - E072337F282B7DB900AF3E16 /* Present */, + E072339D282B7DB900AF3E16 /* Model */, + E072339F282B7DB900AF3E16 /* API */, E0723392282B7DB900AF3E16 /* Repository */, + E072337F282B7DB900AF3E16 /* Present */, E0723397282B7DB900AF3E16 /* Extension */, E072339A282B7DB900AF3E16 /* Common */, - E072339D282B7DB900AF3E16 /* Model */, - E072339F282B7DB900AF3E16 /* API */, E07233A8282B7DB900AF3E16 /* AppDelegate.swift */, E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */, ); @@ -360,6 +374,8 @@ E07233D7282BD5B800AF3E16 /* View */ = { isa = PBXGroup; children = ( + E07233E4282D037500AF3E16 /* WhatsNew */, + E07233E1282CA70E00AF3E16 /* MainEvent */, E07233DC282BDD9C00AF3E16 /* Recommand */, ); path = View; @@ -368,13 +384,34 @@ E07233DC282BDD9C00AF3E16 /* Recommand */ = { isa = PBXGroup; children = ( - E07233D8282BD60200AF3E16 /* RecommandMenuView.swift */, E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */, E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */, + E07233D8282BD60200AF3E16 /* RecommandMenuViewController.swift */, + E07233EB282D0BE500AF3E16 /* RecommandMenuViewModel.swift */, ); path = Recommand; sourceTree = ""; }; + E07233E1282CA70E00AF3E16 /* MainEvent */ = { + isa = PBXGroup; + children = ( + E07233E2282CA71B00AF3E16 /* MainEventViewController.swift */, + E07233E9282D075B00AF3E16 /* MainEventViewModel.swift */, + ); + path = MainEvent; + sourceTree = ""; + }; + E07233E4282D037500AF3E16 /* WhatsNew */ = { + isa = PBXGroup; + children = ( + E07233EF282D0F9D00AF3E16 /* WhatsNewCellView.swift */, + E07233ED282D0F9300AF3E16 /* WhatsNewDataSource.swift */, + E07233E5282D039600AF3E16 /* WhatsNewViewController.swift */, + E07233E7282D03A000AF3E16 /* WhatsNewViewModel.swift */, + ); + path = WhatsNew; + sourceTree = ""; + }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { isa = PBXGroup; children = ( @@ -597,6 +634,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E07233E3282CA71B00AF3E16 /* MainEventViewController.swift in Sources */, E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */, E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */, E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, @@ -617,15 +655,21 @@ E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */, E07233AC282B7DB900AF3E16 /* RootWindow.swift in Sources */, E07233C4282B7DB900AF3E16 /* Response.swift in Sources */, + E07233F0282D0F9D00AF3E16 /* WhatsNewCellView.swift in Sources */, E07233B5282B7DB900AF3E16 /* OrderViewModel.swift in Sources */, - E07233D9282BD60200AF3E16 /* RecommandMenuView.swift in Sources */, + E07233EC282D0BE500AF3E16 /* RecommandMenuViewModel.swift in Sources */, + E07233D9282BD60200AF3E16 /* RecommandMenuViewController.swift in Sources */, E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */, E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, + E07233EE282D0F9300AF3E16 /* WhatsNewDataSource.swift in Sources */, + E07233E6282D039600AF3E16 /* WhatsNewViewController.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */, E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */, E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */, E07233B4282B7DB900AF3E16 /* CategoryTableViewCell.swift in Sources */, + E07233E8282D03A000AF3E16 /* WhatsNewViewModel.swift in Sources */, + E07233EA282D075B00AF3E16 /* MainEventViewModel.swift in Sources */, E07233AE282B7DB900AF3E16 /* StarbucksViewController.swift in Sources */, E07233AB282B7DB900AF3E16 /* HomeViewController.swift in Sources */, E07233AD282B7DB900AF3E16 /* PayViewController.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift index ff4ecfe..05646f0 100644 --- a/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift +++ b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift @@ -8,6 +8,6 @@ import UIKit extension UIColor { - static let green1 = UIColor(red: 0, green: 176 / 255, blue: 111 / 255, alpha: 1) + static let starbuckGreen = UIColor(red: 0, green: 176 / 255, blue: 111 / 255, alpha: 1) static let grey145 = UIColor(white: 145 / 255, alpha: 1) } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 9bc99b4..113bd10 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -21,14 +21,25 @@ class HomeViewController: UIViewController { private let contentStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical + stackView.spacing = 40 return stackView }() - private let recommandMenuView: RecommandMenuView = { - let recommandMenuView = RecommandMenuView(frame: .zero) + private lazy var recommandMenuViewController: RecommandMenuViewController = { + let recommandMenuView = RecommandMenuViewController(viewModel: self.viewModel.recommandMenuViewModel) return recommandMenuView }() + private lazy var mainEventViewController: MainEventViewController = { + let viewController = MainEventViewController(viewModel: viewModel.mainEventViewModel) + return viewController + }() + + private lazy var whatsNewViewController: WhatsNewViewController = { + let viewController = WhatsNewViewController(viewModel: viewModel.whatsNewViewModel) + return viewController + }() + private let viewModel: HomeViewModelProtocol private let disposeBag = DisposeBag() @@ -48,30 +59,14 @@ class HomeViewController: UIViewController { rx.viewDidLoad .bind(to: viewModel.action().loadHome) .disposed(by: disposeBag) - - rx.viewDidLoad - .bind(to: viewModel.action().loadEvent) - .disposed(by: disposeBag) - - viewModel.state().loadedRecommandMenu - .withUnretained(self) - .bind(onNext: { vc, details in - vc.recommandMenuView.updateDataSource(details) - }) - .disposed(by: disposeBag) - - viewModel.state().loadedRecommandImage - .withUnretained(self) - .bind(onNext: { vc, images in - vc.recommandMenuView.updateDataSource(images) - }) - .disposed(by: disposeBag) } private func layout() { view.addSubview(scrollView) scrollView.addSubview(contentStackView) - contentStackView.addArrangedSubview(recommandMenuView) + contentStackView.addArrangedSubview(recommandMenuViewController.view) + contentStackView.addArrangedSubview(mainEventViewController.view) + contentStackView.addArrangedSubview(whatsNewViewController.view) scrollView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) @@ -80,7 +75,7 @@ class HomeViewController: UIViewController { scrollView.contentLayoutGuide.snp.makeConstraints { $0.top.leading.trailing.equalToSuperview() - $0.bottom.equalTo(contentStackView) + $0.bottom.equalTo(contentStackView).offset(50) } contentStackView.snp.makeConstraints { diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 4c372fc..8152493 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -11,12 +11,9 @@ import RxSwift protocol HomeViewModelAction { var loadHome: PublishRelay { get } - var loadEvent: PublishRelay { get } } protocol HomeViewModelState { - var loadedRecommandMenu: PublishRelay<[StarbucksEntity.ProductInfo]> { get } - var loadedRecommandImage: PublishRelay<[[StarbucksEntity.ProductImageInfo]]> { get } } protocol HomeViewModelBinding { @@ -24,18 +21,24 @@ protocol HomeViewModelBinding { func state() -> HomeViewModelState } -typealias HomeViewModelProtocol = HomeViewModelBinding +protocol HomeViewModelProperty { + var whatsNewViewModel: WhatsNewViewModelProtocol { get } + var mainEventViewModel: MainEventViewModelProtocol { get } + var recommandMenuViewModel: RecommandMenuViewModelProtocol { get } +} + +typealias HomeViewModelProtocol = HomeViewModelBinding & HomeViewModelProperty -class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelState { +class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelAction, HomeViewModelState { func action() -> HomeViewModelAction { self } let loadHome = PublishRelay() - let loadEvent = PublishRelay() func state() -> HomeViewModelState { self } - let loadedRecommandMenu = PublishRelay<[StarbucksEntity.ProductInfo]>() - let loadedRecommandImage = PublishRelay<[[StarbucksEntity.ProductImageInfo]]>() + let whatsNewViewModel: WhatsNewViewModelProtocol = WhatsNewViewModel() + let mainEventViewModel: MainEventViewModelProtocol = MainEventViewModel() + let recommandMenuViewModel: RecommandMenuViewModelProtocol = RecommandMenuViewModel() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository @@ -48,58 +51,27 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelAction, HomeViewModelSta model.starbucksRepository.requestHome() } .share() - - requestHome - .compactMap { $0.value } - .bind(onNext: { - }) - .disposed(by: disposeBag) requestHome .compactMap { $0.value?.yourRecommand.products } - .flatMapLatest { ids in - Observable.zip( ids.map { id in - self.starbucksRepository.requestDetail(id).asObservable() - .compactMap { $0.value } - }) - } - .map { $0.compactMap { $0.view } } - .bind(to: loadedRecommandMenu) + .bind(to: recommandMenuViewModel.action().loadedProducts) .disposed(by: disposeBag) requestHome - .compactMap { $0.value?.yourRecommand.products } - .do { print($0) } - .flatMapLatest { ids in - Observable.zip( ids.map { id in - self.starbucksRepository.requestDetailImage(id).asObservable() - .compactMap { $0.value } - }) - } - .map { $0.compactMap { $0.file }.filter { !$0.isEmpty } } - .bind(to: loadedRecommandImage) + .compactMap { $0.value?.displayName } + .bind(to: recommandMenuViewModel.action().loadedUserName) .disposed(by: disposeBag) - - - let requestEvent = action().loadEvent - .withUnretained(self) - .flatMapLatest { model, _ in - model.starbucksRepository.requestEvent() - } - .share() - - requestEvent - .compactMap { $0.value } - .bind(onNext: { - }) + requestHome + .compactMap { $0.value?.mainEvent } + .bind(to: mainEventViewModel.action().loadedEvent) .disposed(by: disposeBag) Observable .merge( - requestHome.compactMap { $0.error }, - requestEvent.compactMap { $0.error }) - .bind(onNext: { + requestHome.compactMap { $0.error } + ) + .bind(onNext: { _ in }) .disposed(by: disposeBag) } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift new file mode 100644 index 0000000..8b4958e --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift @@ -0,0 +1,68 @@ +// +// MainEventView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import RxSwift +import UIKit + +class MainEventViewController: UIViewController { + + private let eventImageView: UIImageView = { + let imageView = UIImageView() + imageView.clipsToBounds = true + imageView.contentMode = .scaleAspectFit + imageView.backgroundColor = .red + return imageView + }() + + @Inject(\.imageManager) private var imageManager: ImageManager + private let viewModel: MainEventViewModelProtocol + private let disposeBag = DisposeBag() + + init(viewModel: MainEventViewModelProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + + bind() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + viewModel.state().loadedMainEventImage + .withUnretained(self) + .flatMapLatest { $0.imageManager.loadImage(url: $1).asObservable() } + .observe(on: MainScheduler.asyncInstance) + .withUnretained(self) + .bind(onNext: { vc, image in + let size = vc.eventImageView.frame.size + let aspect = (size.width / image.size.width) + + vc.eventImageView.image = image + vc.eventImageView.snp.makeConstraints { + $0.height.equalTo(image.size.height * aspect) + } + }) + .disposed(by: disposeBag) + } + + private func layout() { + view.addSubview(eventImageView) + + view.snp.makeConstraints { + $0.bottom.equalTo(eventImageView) + } + + eventImageView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(10) + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift new file mode 100644 index 0000000..0c2bc63 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift @@ -0,0 +1,58 @@ +// +// MainEventViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import Foundation +import RxRelay +import RxSwift + +protocol MainEventViewModelAction { + var loadedEvent: PublishRelay { get } +} + +protocol MainEventViewModelState { + var loadedMainEventImage: PublishRelay { get } +} + +protocol MainEventViewModelBinding { + func action() -> MainEventViewModelAction + func state() -> MainEventViewModelState +} + +typealias MainEventViewModelProtocol = MainEventViewModelBinding + +class MainEventViewModel: MainEventViewModelBinding, MainEventViewModelAction, MainEventViewModelState { + func action() -> MainEventViewModelAction { self } + + var loadedEvent = PublishRelay() + + func state() -> MainEventViewModelState { self } + + let loadedMainEventImage = PublishRelay() + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + init() { + loadedEvent + .map { $0.imageUploadPath.appendingPathComponent($0.thumbnail) } + .bind(to: loadedMainEventImage) + .disposed(by: disposeBag) + +// let requestEvent = action().loadEvent +// .withUnretained(self) +// .flatMapLatest { model, _ in +// model.starbucksRepository.requestEvent() +// } +// .share() +// +// requestEvent +// .compactMap { $0.value } +// .bind(onNext: { +// }) +// .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift index d19a4d3..a2a444a 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift @@ -13,18 +13,18 @@ class RecommandMenuCellView: UICollectionViewCell { private let thumbnailView: UIImageView = { let imageView = UIImageView() - imageView.image = UIImage(named: "mockImage") imageView.backgroundColor = .brown imageView.clipsToBounds = true - imageView.layer.cornerRadius = RecommandMenuView.Constants.cellSize.width / 2 + imageView.layer.cornerRadius = RecommandMenuViewController.Constants.cellSize.width / 2 return imageView }() private let nameLabel: UILabel = { let label = UILabel() - label.font = .systemFont(ofSize: 15) + label.font = .systemFont(ofSize: 13) label.textAlignment = .center label.numberOfLines = 2 + label.lineBreakMode = .byCharWrapping return label }() @@ -51,8 +51,8 @@ class RecommandMenuCellView: UICollectionViewCell { } nameLabel.snp.makeConstraints { - $0.top.equalTo(thumbnailView.snp.bottom) - $0.leading.trailing.bottom.equalToSuperview() + $0.top.equalTo(thumbnailView.snp.bottom).offset(13) + $0.leading.trailing.equalToSuperview() } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift similarity index 59% rename from Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift rename to Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift index ced84d9..750dc8e 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift @@ -5,16 +5,16 @@ // Created by seongha shin on 2022/05/11. // +import RxSwift import UIKit -class RecommandMenuView: UIView { +class RecommandMenuViewController: UIViewController { enum Constants { static let cellSize = CGSize(width: 130, height: 180) } private let titleLabel: UILabel = { let label = UILabel() - label.text = "싱하 님을 위한 추천 메뉴" label.textAlignment = .left label.font = .systemFont(ofSize: 24, weight: .bold) label.textColor = .black @@ -24,7 +24,7 @@ class RecommandMenuView: UIView { private let menuCollectionView: UICollectionView = { let flowLayout = UICollectionViewFlowLayout() flowLayout.scrollDirection = .horizontal - flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) + flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) flowLayout.minimumLineSpacing = 15 let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) collectionView.isScrollEnabled = true @@ -33,10 +33,15 @@ class RecommandMenuView: UIView { return collectionView }() + private let viewModel: RecommandMenuViewModelProtocol + private let disposeBag = DisposeBag() private let dataSource = RecommandMenuDataSource() - override init(frame: CGRect) { - super.init(frame: frame) + init(viewModel: RecommandMenuViewModelProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + + bind() attribute() layout() } @@ -45,6 +50,31 @@ class RecommandMenuView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func bind() { + viewModel.state().loadedRecommandMenu + .withUnretained(self) + .bind(onNext: { vc, products in + vc.dataSource.updateProducts(products) + vc.menuCollectionView.reloadData() + }) + .disposed(by: disposeBag) + + viewModel.state().loadedRecommandImage + .withUnretained(self) + .bind(onNext: { vc, images in + vc.dataSource.updateProductImages(images) + vc.menuCollectionView.reloadData() + }) + .disposed(by: disposeBag) + + viewModel.state().displayTitle + .withUnretained(self) + .bind(onNext: { vc, title in + vc.titleLabel.text = title + }) + .disposed(by: disposeBag) + } private func attribute() { menuCollectionView.dataSource = dataSource @@ -53,10 +83,10 @@ class RecommandMenuView: UIView { } private func layout() { - addSubview(titleLabel) - addSubview(menuCollectionView) + view.addSubview(titleLabel) + view.addSubview(menuCollectionView) - snp.makeConstraints { + view.snp.makeConstraints { $0.bottom.equalTo(menuCollectionView) } @@ -71,23 +101,9 @@ class RecommandMenuView: UIView { $0.height.equalTo(Constants.cellSize.height) } } - - func updateDataSource(_ products: [StarbucksEntity.ProductInfo]) { - self.dataSource.updateProducts(products) - DispatchQueue.main.async { - self.menuCollectionView.reloadData() - } - } - - func updateDataSource( _ images: [[StarbucksEntity.ProductImageInfo]]) { - self.dataSource.updateProductImages(images) - DispatchQueue.main.async { - self.menuCollectionView.reloadData() - } - } } -extension RecommandMenuView: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { +extension RecommandMenuViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { Constants.cellSize } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift new file mode 100644 index 0000000..8282b08 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift @@ -0,0 +1,74 @@ +// +// RecommandMenuViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import Foundation +import RxRelay +import RxSwift + +protocol RecommandMenuViewModelAction { + var loadedProducts: PublishRelay<[String]> { get } + var loadedUserName: PublishRelay { get } +} + +protocol RecommandMenuViewModelState { + var loadedRecommandMenu: PublishRelay<[StarbucksEntity.ProductInfo]> { get } + var loadedRecommandImage: PublishRelay<[[StarbucksEntity.ProductImageInfo]]> { get } + var displayTitle: PublishRelay { get } +} + +protocol RecommandMenuViewModelBinding { + func action() -> RecommandMenuViewModelAction + func state() -> RecommandMenuViewModelState +} + +typealias RecommandMenuViewModelProtocol = RecommandMenuViewModelBinding + +class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewModelAction, RecommandMenuViewModelState { + + func action() -> RecommandMenuViewModelAction { self } + + let loadedProducts = PublishRelay<[String]>() + let loadedUserName = PublishRelay() + + func state() -> RecommandMenuViewModelState { self } + + let loadedRecommandMenu = PublishRelay<[StarbucksEntity.ProductInfo]>() + let loadedRecommandImage = PublishRelay<[[StarbucksEntity.ProductImageInfo]]>() + let displayTitle = PublishRelay() + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + init() { + loadedProducts + .flatMapLatest { ids in + Observable.zip( ids.map { id in + self.starbucksRepository.requestDetail(id).asObservable() + .compactMap { $0.value } + }) + } + .map { $0.compactMap { $0.view } } + .bind(to: loadedRecommandMenu) + .disposed(by: disposeBag) + + loadedProducts + .flatMapLatest { ids in + Observable.zip( ids.map { id in + self.starbucksRepository.requestDetailImage(id).asObservable() + .compactMap { $0.value } + }) + } + .map { $0.compactMap { $0.file }.filter { !$0.isEmpty } } + .bind(to: loadedRecommandImage) + .disposed(by: disposeBag) + + loadedUserName + .map { "\($0)님을 위한 추천 메뉴" } + .bind(to: displayTitle) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift new file mode 100644 index 0000000..0a4c5e4 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift @@ -0,0 +1,74 @@ +// +// WhatsNewCellView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import RxSwift +import UIKit + +class WhatsNewCellView: UICollectionViewCell { + static let identifier = "WhatsNewCellView" + + private let thumbnailView: UIImageView = { + let imageView = UIImageView() + imageView.backgroundColor = .brown + imageView.contentMode = .scaleToFill + return imageView + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 16, weight: .bold) + label.textAlignment = .left + label.textColor = .black + return label + }() + + @Inject(\.imageManager) private var imageManager: ImageManager + private let disposeBag = DisposeBag() + + override init(frame: CGRect) { + super.init(frame: frame) + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("Init with coder is unavailable") + } + + private func layout() { + addSubview(thumbnailView) + addSubview(titleLabel) + + let cellSize = frame.size + let imageAspect = 480.0 / 720.0 + + thumbnailView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(cellSize.width * imageAspect) + } + + titleLabel.snp.makeConstraints { + $0.top.equalTo(thumbnailView.snp.bottom) + $0.leading.trailing.bottom.equalToSuperview() + } + } + + func setTitle(_ title: String) { + titleLabel.text = title + } + + func setThumbnail(uploadPath: URL, thumbnailName: String) { + let url = uploadPath.appendingPathComponent("/upload/promotion/" + thumbnailName) + imageManager.loadImage(url: url).asObservable() + .withUnretained(self) + .observe(on: MainScheduler.asyncInstance) + .bind(onNext: { cell, image in + cell.thumbnailView.image = image + }) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift new file mode 100644 index 0000000..371ca6a --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift @@ -0,0 +1,31 @@ +// +// WhatsNewDataSource.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import UIKit + +class WhatsNewDataSource: NSObject, UICollectionViewDataSource { + private var events: [StarbucksEntity.Event] = [] + + func updateEvents(_ events: [StarbucksEntity.Event]) { + self.events = events + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + events.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WhatsNewCellView.identifier, for: indexPath) as? WhatsNewCellView else { + return UICollectionViewCell() + } + + let event = events[indexPath.item] + cell.setTitle(event.title) + cell.setThumbnail(uploadPath: event.imageUploadPath, thumbnailName: event.thumbnail) + return cell + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift new file mode 100644 index 0000000..7cb918b --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift @@ -0,0 +1,114 @@ +// +// WhatsNewViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import RxSwift +import UIKit + +class WhatsNewViewController: UIViewController { + enum Constants { + static let cellSize = CGSize(width: 230, height: 200) + } + + private let headerView = UIView() + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "What's New" + label.font = .systemFont(ofSize: 24, weight: .bold) + label.textColor = .black + return label + }() + + private let seeAllButton: UIButton = { + let button = UIButton() + button.setTitle("See all", for: .normal) + button.setTitleColor(.starbuckGreen, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 13, weight: .bold) + return button + }() + + private let eventCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .horizontal + flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) + flowLayout.minimumLineSpacing = 15 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.isScrollEnabled = true + collectionView.register(WhatsNewCellView.self, forCellWithReuseIdentifier: WhatsNewCellView.identifier) + collectionView.showsHorizontalScrollIndicator = false + return collectionView + }() + + private let viewModel: WhatsNewViewModelProtocol + private let disposeBag = DisposeBag() + private let dataSource = WhatsNewDataSource() + + init(viewModel: WhatsNewViewModelProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + bind() + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + rx.viewDidLoad + .bind(to: viewModel.action().loadEvent) + .disposed(by: disposeBag) + + viewModel.state().loadedEvent + .withUnretained(self) + .bind(onNext: { vc, events in + vc.dataSource.updateEvents(events) + vc.eventCollectionView.reloadData() + }) + .disposed(by: disposeBag) + } + + private func attribute() { + eventCollectionView.dataSource = dataSource + eventCollectionView.delegate = self + eventCollectionView.reloadData() + } + + private func layout() { + view.addSubview(titleLabel) + view.addSubview(eventCollectionView) + view.addSubview(seeAllButton) + + view.snp.makeConstraints { + $0.bottom.equalTo(eventCollectionView) + } + + titleLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20) + $0.top.equalToSuperview() + } + + seeAllButton.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(20) + $0.centerY.equalTo(titleLabel) + } + + eventCollectionView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(15) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(Constants.cellSize.height) + } + } +} + +extension WhatsNewViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + Constants.cellSize + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift new file mode 100644 index 0000000..ccd6ad3 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift @@ -0,0 +1,60 @@ +// +// WhatsNewViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import Foundation +import RxRelay +import RxSwift + +protocol WhatsNewViewModelAction { + var loadEvent: PublishRelay { get } +} + +protocol WhatsNewViewModelState { + var loadedEvent: PublishRelay<[StarbucksEntity.Event]> { get } +} + +protocol WhatsNewViewModelBinding { + func action() -> WhatsNewViewModelAction + func state() -> WhatsNewViewModelState +} + +typealias WhatsNewViewModelProtocol = WhatsNewViewModelBinding + +class WhatsNewViewModel: WhatsNewViewModelBinding, WhatsNewViewModelAction, WhatsNewViewModelState { + func action() -> WhatsNewViewModelAction { self } + + let loadEvent = PublishRelay() + + func state() -> WhatsNewViewModelState { self } + + let loadedEvent = PublishRelay<[StarbucksEntity.Event]>() + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + init() { + let requestEvent = action().loadEvent + .withUnretained(self) + .flatMapLatest { model, _ in + model.starbucksRepository.requestEvent() + } + .share() + + requestEvent + .compactMap { $0.value?.list } + .bind(to: loadedEvent) + .disposed(by: disposeBag) + + Observable + .merge( + requestEvent.compactMap { $0.error } + ) + .bind(onNext: { + }) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index b60cf8d..1aef749 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -11,7 +11,7 @@ class StarbucksViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white - tabBar.tintColor = .green1 + tabBar.tintColor = .starbuckGreen tabBar.unselectedItemTintColor = .grey145 setUpTabBar() } From 0ec4e10646c241b23dee1f9a44a262996f40dae9 Mon Sep 17 00:00:00 2001 From: shingha Date: Thu, 12 May 2022 23:54:22 +0900 Subject: [PATCH 33/57] =?UTF-8?q?[STAR-13]=20feature:=20stickyView=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84,=20=EC=83=81=EB=8B=A8=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84,=20WhatsNew=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 28 ++++ .../homeTopBg.imageset/Contents.json | 21 +++ .../homeTopBg.imageset/homeTopBg.jpeg | Bin 0 -> 26799 bytes .../Sources/Common/View/GradientView.swift | 21 +++ .../NSMutableAttributedString+Extension.swift | 38 +++++ .../Sources/Extension/UIColor+Extension.swift | 1 + .../Present/Home/HomeViewController.swift | 76 +++++++++- .../Sources/Present/Home/HomeViewModel.swift | 9 +- .../Home/View/HomeInfoView/HomeInfoView.swift | 136 ++++++++++++++++++ .../RecommandMenuViewController.swift | 5 +- .../Recommand/RecommandMenuViewModel.swift | 11 +- .../WhatsNew/WhatsNewViewController.swift | 2 +- .../TabBar/StarbucksViewController.swift | 4 +- 13 files changed, 340 insertions(+), 12 deletions(-) create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/Contents.json create mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/homeTopBg.jpeg create mode 100644 Starbucks/Starbucks/Sources/Common/View/GradientView.swift create mode 100644 Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index c1791b2..f6d234d 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -54,6 +54,9 @@ E07233EC282D0BE500AF3E16 /* RecommandMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233EB282D0BE500AF3E16 /* RecommandMenuViewModel.swift */; }; E07233EE282D0F9300AF3E16 /* WhatsNewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233ED282D0F9300AF3E16 /* WhatsNewDataSource.swift */; }; E07233F0282D0F9D00AF3E16 /* WhatsNewCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233EF282D0F9D00AF3E16 /* WhatsNewCellView.swift */; }; + E07233F2282D235100AF3E16 /* NSMutableAttributedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233F1282D235100AF3E16 /* NSMutableAttributedString+Extension.swift */; }; + E07233F5282D3BF000AF3E16 /* HomeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233F4282D3BF000AF3E16 /* HomeInfoView.swift */; }; + E07233F8282D516200AF3E16 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E07233F7282D516200AF3E16 /* GradientView.swift */; }; E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08BB6AE28294347005ADEFA /* StarbucksTests.swift */; }; /* End PBXBuildFile section */ @@ -120,6 +123,9 @@ E07233EB282D0BE500AF3E16 /* RecommandMenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommandMenuViewModel.swift; sourceTree = ""; }; E07233ED282D0F9300AF3E16 /* WhatsNewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewDataSource.swift; sourceTree = ""; }; E07233EF282D0F9D00AF3E16 /* WhatsNewCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewCellView.swift; sourceTree = ""; }; + E07233F1282D235100AF3E16 /* NSMutableAttributedString+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extension.swift"; sourceTree = ""; }; + E07233F4282D3BF000AF3E16 /* HomeInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeInfoView.swift; sourceTree = ""; }; + E07233F7282D516200AF3E16 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarbucksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E08BB6AE28294347005ADEFA /* StarbucksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbucksTests.swift; sourceTree = ""; }; EB77E908CE60F89C1EB53248 /* Pods-Starbucks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.release.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.release.xcconfig"; sourceTree = ""; }; @@ -317,6 +323,7 @@ children = ( E0723398282B7DB900AF3E16 /* UIColor+Extension.swift */, E0723399282B7DB900AF3E16 /* Result+Extension.swift */, + E07233F1282D235100AF3E16 /* NSMutableAttributedString+Extension.swift */, ); path = Extension; sourceTree = ""; @@ -324,6 +331,7 @@ E072339A282B7DB900AF3E16 /* Common */ = { isa = PBXGroup; children = ( + E07233F6282D515800AF3E16 /* View */, E07233D2282BCBA900AF3E16 /* PropertyWrapper */, E072339B282B7DB900AF3E16 /* Log.swift */, E07233D0282BCB5E00AF3E16 /* Container.swift */, @@ -374,6 +382,7 @@ E07233D7282BD5B800AF3E16 /* View */ = { isa = PBXGroup; children = ( + E07233F3282D3BD300AF3E16 /* HomeInfoView */, E07233E4282D037500AF3E16 /* WhatsNew */, E07233E1282CA70E00AF3E16 /* MainEvent */, E07233DC282BDD9C00AF3E16 /* Recommand */, @@ -412,6 +421,22 @@ path = WhatsNew; sourceTree = ""; }; + E07233F3282D3BD300AF3E16 /* HomeInfoView */ = { + isa = PBXGroup; + children = ( + E07233F4282D3BF000AF3E16 /* HomeInfoView.swift */, + ); + path = HomeInfoView; + sourceTree = ""; + }; + E07233F6282D515800AF3E16 /* View */ = { + isa = PBXGroup; + children = ( + E07233F7282D516200AF3E16 /* GradientView.swift */, + ); + path = View; + sourceTree = ""; + }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { isa = PBXGroup; children = ( @@ -643,6 +668,7 @@ E07233DB282BD71900AF3E16 /* RecommandMenuCellView.swift in Sources */, E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */, E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */, + E07233F5282D3BF000AF3E16 /* HomeInfoView.swift in Sources */, E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */, E07233BB282B7DB900AF3E16 /* Log.swift in Sources */, E07233B1282B7DB900AF3E16 /* OrderTableViewDelegate.swift in Sources */, @@ -650,6 +676,7 @@ E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */, E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */, E07233DE282BDDB200AF3E16 /* RecommandMenuDataSource.swift in Sources */, + E07233F8282D516200AF3E16 /* GradientView.swift in Sources */, E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */, E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */, @@ -664,6 +691,7 @@ E07233EE282D0F9300AF3E16 /* WhatsNewDataSource.swift in Sources */, E07233E6282D039600AF3E16 /* WhatsNewViewController.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, + E07233F2282D235100AF3E16 /* NSMutableAttributedString+Extension.swift in Sources */, E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */, E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */, E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */, diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/Contents.json new file mode 100644 index 0000000..c1db58b --- /dev/null +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "homeTopBg.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/homeTopBg.jpeg b/Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/homeTopBg.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e85a76ee3d2047fa7dc6333c979b246e6a82d946 GIT binary patch literal 26799 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 literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/Common/View/GradientView.swift b/Starbucks/Starbucks/Sources/Common/View/GradientView.swift new file mode 100644 index 0000000..bc4fe79 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/View/GradientView.swift @@ -0,0 +1,21 @@ +// +// GradientView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import UIKit + +class GradientView: UIView { + override class var layerClass: AnyClass { + CAGradientLayer.self + } + + func set(colors: [UIColor], startPoint: CGPoint, endPoint: CGPoint) { + let gradientLayer = layer as? CAGradientLayer + gradientLayer?.colors = colors.compactMap { $0.cgColor } + gradientLayer?.startPoint = startPoint + gradientLayer?.endPoint = endPoint + } +} diff --git a/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift new file mode 100644 index 0000000..3f6b8af --- /dev/null +++ b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift @@ -0,0 +1,38 @@ +// +// NSMutableAttributedString+Extension.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import UIKit + +enum AttributedStringOption { + case font(_ font: UIFont) + case foreground(color: UIColor) + case background(color: UIColor) + case underLined + case strikethrough +} + +extension NSMutableAttributedString { + func attributedOption(_ string: String, options: [AttributedStringOption] = []) -> NSMutableAttributedString { + var attributes = [NSAttributedString.Key: Any]() + options.forEach { option in + switch option { + case .font(let font): + attributes[.font] = font + case .foreground(let color): + attributes[.foregroundColor] = color + case .background(let color): + attributes[.backgroundColor] = color + case .underLined: + attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue + case .strikethrough: + attributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue + } + } + self.append(NSAttributedString(string: string, attributes: attributes)) + return self + } +} diff --git a/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift index 05646f0..c379d9b 100644 --- a/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift +++ b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift @@ -9,5 +9,6 @@ import UIKit extension UIColor { static let starbuckGreen = UIColor(red: 0, green: 176 / 255, blue: 111 / 255, alpha: 1) + static let brown1 = UIColor(red: 145 / 255, green: 120 / 255, blue: 75 / 255, alpha: 1) static let grey145 = UIColor(white: 145 / 255, alpha: 1) } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 113bd10..4fc9f66 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -7,14 +7,21 @@ import RxAppState import RxSwift +import SnapKit import UIKit class HomeViewController: UIViewController { + enum Constants { + static let stickyViewHeight = 50.0 + static let homeInfoViewHeight = 250.0 + } private let scrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.isPagingEnabled = false scrollView.showsHorizontalScrollIndicator = true + scrollView.contentInsetAdjustmentBehavior = .never + scrollView.bounces = false return scrollView }() @@ -25,6 +32,11 @@ class HomeViewController: UIViewController { return stackView }() + private let homeInfoView: HomeInfoView = { + let view = HomeInfoView(stickViewHeight: Constants.stickyViewHeight) + return view + }() + private lazy var recommandMenuViewController: RecommandMenuViewController = { let recommandMenuView = RecommandMenuViewController(viewModel: self.viewModel.recommandMenuViewModel) return recommandMenuView @@ -42,11 +54,13 @@ class HomeViewController: UIViewController { private let viewModel: HomeViewModelProtocol private let disposeBag = DisposeBag() + private var topSafeArea: CGFloat = 0 init(viewModel: HomeViewModelProtocol) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) bind() + attribute() layout() } @@ -59,27 +73,83 @@ class HomeViewController: UIViewController { rx.viewDidLoad .bind(to: viewModel.action().loadHome) .disposed(by: disposeBag) + + rx.viewDidAppear + .map { _ in } + .withUnretained(self) + .bind(onNext: { vc, _ in + vc.navigationController?.isNavigationBarHidden = true + }) + .disposed(by: disposeBag) + + rx.viewDidAppear + .withUnretained(self) + .bind(onNext: { vc, _ in + let window = UIApplication.shared.windows[0] + vc.topSafeArea = window.safeAreaInsets.top + }) + .disposed(by: disposeBag) + + viewModel.state().titleMessage + .withUnretained(self) + .bind(onNext: { vc, message in + vc.homeInfoView.setMessage(message) + }) + .disposed(by: disposeBag) + } + + private func attribute() { + scrollView.delegate = self } private func layout() { view.addSubview(scrollView) + view.addSubview(homeInfoView) scrollView.addSubview(contentStackView) contentStackView.addArrangedSubview(recommandMenuViewController.view) contentStackView.addArrangedSubview(mainEventViewController.view) contentStackView.addArrangedSubview(whatsNewViewController.view) + contentStackView.setCustomSpacing(Constants.stickyViewHeight, after: homeInfoView) + scrollView.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide) + $0.top.equalToSuperview() $0.leading.trailing.bottom.equalToSuperview() } scrollView.contentLayoutGuide.snp.makeConstraints { $0.top.leading.trailing.equalToSuperview() - $0.bottom.equalTo(contentStackView).offset(50) + $0.bottom.equalTo(contentStackView).offset(150) } contentStackView.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() + $0.top.equalToSuperview().offset(Constants.homeInfoViewHeight) + $0.leading.trailing.equalToSuperview() + } + + homeInfoView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(Constants.homeInfoViewHeight) + } + } +} + +extension HomeViewController: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + var offset = 0.0 + + let limiteScrollValue = Constants.homeInfoViewHeight - Constants.stickyViewHeight - topSafeArea + homeInfoView.setAlpha(1.0 - scrollView.contentOffset.y / limiteScrollValue) + + if scrollView.contentOffset.y < limiteScrollValue { + offset = (scrollView.contentOffset.y * -1) + } else { + offset = topSafeArea - Constants.homeInfoViewHeight + Constants.stickyViewHeight + } + + homeInfoView.snp.updateConstraints { + $0.top.equalToSuperview().offset(offset) } } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 8152493..0f84714 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -14,6 +14,7 @@ protocol HomeViewModelAction { } protocol HomeViewModelState { + var titleMessage: PublishRelay { get } } protocol HomeViewModelBinding { @@ -36,6 +37,8 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA func state() -> HomeViewModelState { self } + let titleMessage = PublishRelay() + let whatsNewViewModel: WhatsNewViewModelProtocol = WhatsNewViewModel() let mainEventViewModel: MainEventViewModelProtocol = MainEventViewModel() let recommandMenuViewModel: RecommandMenuViewModelProtocol = RecommandMenuViewModel() @@ -59,7 +62,11 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA requestHome .compactMap { $0.value?.displayName } - .bind(to: recommandMenuViewModel.action().loadedUserName) + .withUnretained(self) + .bind(onNext: { model, name in + model.titleMessage.accept("\(name)님\n오늘 하루도 고생 많으셨어요!") + model.recommandMenuViewModel.action().loadedUserName.accept(name) + }) .disposed(by: disposeBag) requestHome diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift new file mode 100644 index 0000000..f4e7f06 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift @@ -0,0 +1,136 @@ +// +// HomeInfoView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import UIKit + +class HomeInfoView: UIView { + + private let infoView = UIView() + + private let backgroundView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "homeTopBg") + return imageView + }() + + private let gradientView: GradientView = { + let view = GradientView() + let colors = [ + UIColor(red: 1, green: 1, blue: 1, alpha: 1), + UIColor(red: 1, green: 1, blue: 1, alpha: 0) + ] + + view.set( + colors: colors, + startPoint: CGPoint(x: 0.5, y: 1.0), + endPoint: CGPoint(x: 0.5, y: 0) + ) + return view + }() + + private let messageLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 2 + label.font = .systemFont(ofSize: 24, weight: .bold) + label.textColor = .black + label.textAlignment = .left + return label + }() + + private let stickView = UIView() + + private let whatsNewButton: UIButton = { + let button = UIButton() + button.setTitle("What's New", for: .normal) + button.setTitleColor(.black, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 13, weight: .bold) + return button + }() + + private let bottomBar: UIView = { + let view = UIView() + view.backgroundColor = .darkGray + view.alpha = 0 + return view + }() + + private let stickViewHeight: CGFloat + + init(stickViewHeight: CGFloat) { + self.stickViewHeight = stickViewHeight + super.init(frame: .zero) + + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func attribute() { + backgroundColor = .white + } + + private func layout() { + addSubview(infoView) + infoView.addSubview(backgroundView) + infoView.addSubview(gradientView) + infoView.addSubview(messageLabel) + + addSubview(stickView) + stickView.addSubview(whatsNewButton) + stickView.addSubview(bottomBar) + + backgroundView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + gradientView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + infoView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(stickView.snp.top) + } + + messageLabel.snp.makeConstraints { + $0.leading.equalToSuperview().inset(20) + $0.trailing.equalToSuperview().inset(20) + $0.top.bottom.equalToSuperview() + } + + stickView.snp.makeConstraints { + $0.leading.trailing.bottom.equalToSuperview() + $0.height.equalTo(stickViewHeight) + } + + whatsNewButton.snp.makeConstraints { + $0.top.bottom.equalToSuperview() + $0.leading.equalToSuperview().offset(20) + } + + bottomBar.snp.makeConstraints { + $0.top.equalTo(whatsNewButton.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1) + } + } + + func setMessage(_ message: String) { + messageLabel.text = message + } + + func setAlpha(_ alpha: CGFloat) { + let rangeAlpha = alpha < 0 ? 0 : alpha + infoView.alpha = rangeAlpha + bottomBar.alpha = (1 - rangeAlpha) * 0.5 + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift index 750dc8e..4f0b481 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift @@ -71,7 +71,7 @@ class RecommandMenuViewController: UIViewController { viewModel.state().displayTitle .withUnretained(self) .bind(onNext: { vc, title in - vc.titleLabel.text = title + vc.titleLabel.attributedText = title }) .disposed(by: disposeBag) } @@ -91,8 +91,9 @@ class RecommandMenuViewController: UIViewController { } titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(20) $0.leading.equalToSuperview().offset(20) - $0.top.trailing.equalToSuperview() + $0.trailing.equalToSuperview() } menuCollectionView.snp.makeConstraints { diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift index 8282b08..6a54354 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift @@ -17,7 +17,7 @@ protocol RecommandMenuViewModelAction { protocol RecommandMenuViewModelState { var loadedRecommandMenu: PublishRelay<[StarbucksEntity.ProductInfo]> { get } var loadedRecommandImage: PublishRelay<[[StarbucksEntity.ProductImageInfo]]> { get } - var displayTitle: PublishRelay { get } + var displayTitle: PublishRelay { get } } protocol RecommandMenuViewModelBinding { @@ -38,7 +38,7 @@ class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewMo let loadedRecommandMenu = PublishRelay<[StarbucksEntity.ProductInfo]>() let loadedRecommandImage = PublishRelay<[[StarbucksEntity.ProductImageInfo]]>() - let displayTitle = PublishRelay() + let displayTitle = PublishRelay() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() @@ -67,8 +67,11 @@ class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewMo .disposed(by: disposeBag) loadedUserName - .map { "\($0)님을 위한 추천 메뉴" } + .map { NSMutableAttributedString() + .attributedOption($0, options: [.foreground(color: .brown1)]) + .attributedOption("님을 위한 추천 메뉴") + } .bind(to: displayTitle) - .disposed(by: disposeBag) + .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift index 7cb918b..01ff44f 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift @@ -10,7 +10,7 @@ import UIKit class WhatsNewViewController: UIViewController { enum Constants { - static let cellSize = CGSize(width: 230, height: 200) + static let cellSize = CGSize(width: 220, height: 200) } private let headerView = UIView() diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index 1aef749..5b3b94b 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -17,7 +17,9 @@ class StarbucksViewController: UITabBarController { } private func setUpTabBar() { - let homeViewController = UINavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())) +// let homeViewController = UINavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())) + let homeViewController = HomeViewController(viewModel: HomeViewModel()) + homeViewController homeViewController.tabBarItem.title = "Home" homeViewController.tabBarItem.image = UIImage(named: "ic_temp") From a33141eb9e0e3552fa70c9862dafda9e3279865b Mon Sep 17 00:00:00 2001 From: shingha Date: Fri, 13 May 2022 10:56:23 +0900 Subject: [PATCH 34/57] =?UTF-8?q?[STAR-13]=20feature:=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=EC=84=A0=ED=83=9D,=20=EB=A9=94=EC=9D=B8=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20pushController=20?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 4 ++-- .../NSMutableAttributedString+Extension.swift | 2 +- .../Present/Home/HomeViewController.swift | 10 +++++++- .../Sources/Present/Home/HomeViewModel.swift | 23 +++++++++++++++++++ .../MainEvent/MainEventViewController.swift | 15 ++++++++++++ .../View/MainEvent/MainEventViewModel.swift | 2 ++ .../RecommandMenuViewController.swift | 4 ++++ .../Recommand/RecommandMenuViewModel.swift | 6 +++-- .../WhatsNew/WhatsNewViewController.swift | 4 ++++ .../View/WhatsNew/WhatsNewViewModel.swift | 4 +++- .../TabBar/StarbucksViewController.swift | 4 +--- 11 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index f6d234d..06be425 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -866,7 +866,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -895,7 +895,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; diff --git a/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift index 3f6b8af..3d72f9c 100644 --- a/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift +++ b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift @@ -16,7 +16,7 @@ enum AttributedStringOption { } extension NSMutableAttributedString { - func attributedOption(_ string: String, options: [AttributedStringOption] = []) -> NSMutableAttributedString { + func addString(_ string: String, options: [AttributedStringOption] = []) -> NSMutableAttributedString { var attributes = [NSAttributedString.Key: Any]() options.forEach { option in switch option { diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 4fc9f66..5014b09 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -14,6 +14,7 @@ class HomeViewController: UIViewController { enum Constants { static let stickyViewHeight = 50.0 static let homeInfoViewHeight = 250.0 + static let bottomOffset = 100.0 } private let scrollView: UIScrollView = { @@ -119,7 +120,7 @@ class HomeViewController: UIViewController { scrollView.contentLayoutGuide.snp.makeConstraints { $0.top.leading.trailing.equalToSuperview() - $0.bottom.equalTo(contentStackView).offset(150) + $0.bottom.equalTo(contentStackView).offset(Constants.bottomOffset) } contentStackView.snp.makeConstraints { @@ -132,6 +133,13 @@ class HomeViewController: UIViewController { $0.leading.trailing.equalToSuperview() $0.height.equalTo(Constants.homeInfoViewHeight) } + + viewModel.state().presentProductDetailView + .withUnretained(self) + .bind(onNext: { vc, product in + //TODO: Present Detail View + }) + .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 0f84714..ca655d9 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -15,6 +15,7 @@ protocol HomeViewModelAction { protocol HomeViewModelState { var titleMessage: PublishRelay { get } + var presentProductDetailView: PublishRelay { get } } protocol HomeViewModelBinding { @@ -38,6 +39,7 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA func state() -> HomeViewModelState { self } let titleMessage = PublishRelay() + let presentProductDetailView = PublishRelay() let whatsNewViewModel: WhatsNewViewModelProtocol = WhatsNewViewModel() let mainEventViewModel: MainEventViewModelProtocol = MainEventViewModel() @@ -46,6 +48,7 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() + private var homeData: StarbucksEntity.Home? init() { let requestHome = action().loadHome @@ -55,6 +58,14 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA } .share() + requestHome + .compactMap { $0.value } + .withUnretained(self) + .bind(onNext: { model, homeData in + model.homeData = homeData + }) + .disposed(by: disposeBag) + requestHome .compactMap { $0.value?.yourRecommand.products } .bind(to: recommandMenuViewModel.action().loadedProducts) @@ -81,5 +92,17 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA .bind(onNext: { _ in }) .disposed(by: disposeBag) + + recommandMenuViewModel.action().selectedProduct + .withUnretained(self) + .compactMap { model, index in model.homeData?.yourRecommand.products?[index] } + .bind(to: presentProductDetailView) + .disposed(by: disposeBag) + + mainEventViewModel.action().tappedEvent + .bind(onNext: { + //TODO: Tapped Main Event + }) + .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift index 8b4958e..d21feeb 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift @@ -18,6 +18,12 @@ class MainEventViewController: UIViewController { return imageView }() + private let eventButton: UIButton = { + let button = UIButton() + + return button + }() + @Inject(\.imageManager) private var imageManager: ImageManager private let viewModel: MainEventViewModelProtocol private let disposeBag = DisposeBag() @@ -51,10 +57,15 @@ class MainEventViewController: UIViewController { } }) .disposed(by: disposeBag) + + eventButton.rx.tap + .bind(to: viewModel.action().tappedEvent) + .disposed(by: disposeBag) } private func layout() { view.addSubview(eventImageView) + view.addSubview(eventButton) view.snp.makeConstraints { $0.bottom.equalTo(eventImageView) @@ -64,5 +75,9 @@ class MainEventViewController: UIViewController { $0.top.equalToSuperview() $0.leading.trailing.equalToSuperview().inset(10) } + + eventButton.snp.makeConstraints { + $0.edges.equalTo(eventImageView) + } } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift index 0c2bc63..5004a28 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift @@ -11,6 +11,7 @@ import RxSwift protocol MainEventViewModelAction { var loadedEvent: PublishRelay { get } + var tappedEvent: PublishRelay { get } } protocol MainEventViewModelState { @@ -28,6 +29,7 @@ class MainEventViewModel: MainEventViewModelBinding, MainEventViewModelAction, M func action() -> MainEventViewModelAction { self } var loadedEvent = PublishRelay() + var tappedEvent = PublishRelay() func state() -> MainEventViewModelState { self } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift index 4f0b481..50ca05a 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift @@ -108,4 +108,8 @@ extension RecommandMenuViewController: UICollectionViewDelegate, UICollectionVie func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { Constants.cellSize } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + viewModel.action().selectedProduct.accept(indexPath.item) + } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift index 6a54354..a8c3fe9 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift @@ -12,6 +12,7 @@ import RxSwift protocol RecommandMenuViewModelAction { var loadedProducts: PublishRelay<[String]> { get } var loadedUserName: PublishRelay { get } + var selectedProduct: PublishRelay { get } } protocol RecommandMenuViewModelState { @@ -33,6 +34,7 @@ class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewMo let loadedProducts = PublishRelay<[String]>() let loadedUserName = PublishRelay() + let selectedProduct = PublishRelay() func state() -> RecommandMenuViewModelState { self } @@ -68,8 +70,8 @@ class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewMo loadedUserName .map { NSMutableAttributedString() - .attributedOption($0, options: [.foreground(color: .brown1)]) - .attributedOption("님을 위한 추천 메뉴") + .addString($0, options: [.foreground(color: .brown1)]) + .addString("님을 위한 추천 메뉴") } .bind(to: displayTitle) .disposed(by: disposeBag) diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift index 01ff44f..9d2f8bf 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift @@ -111,4 +111,8 @@ extension WhatsNewViewController: UICollectionViewDelegate, UICollectionViewDele func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { Constants.cellSize } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + viewModel.action().selectedEvent.accept(indexPath.item) + } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift index ccd6ad3..6a50053 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift @@ -11,6 +11,7 @@ import RxSwift protocol WhatsNewViewModelAction { var loadEvent: PublishRelay { get } + var selectedEvent: PublishRelay { get } } protocol WhatsNewViewModelState { @@ -28,6 +29,7 @@ class WhatsNewViewModel: WhatsNewViewModelBinding, WhatsNewViewModelAction, What func action() -> WhatsNewViewModelAction { self } let loadEvent = PublishRelay() + let selectedEvent = PublishRelay() func state() -> WhatsNewViewModelState { self } @@ -53,7 +55,7 @@ class WhatsNewViewModel: WhatsNewViewModelBinding, WhatsNewViewModelAction, What .merge( requestEvent.compactMap { $0.error } ) - .bind(onNext: { + .bind(onNext: { _ in }) .disposed(by: disposeBag) } diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index 5b3b94b..1aef749 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -17,9 +17,7 @@ class StarbucksViewController: UITabBarController { } private func setUpTabBar() { -// let homeViewController = UINavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())) - let homeViewController = HomeViewController(viewModel: HomeViewModel()) - homeViewController + let homeViewController = UINavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())) homeViewController.tabBarItem.title = "Home" homeViewController.tabBarItem.image = UIImage(named: "ic_temp") From 07e40acc1d878ab135b619bf4fb88a1e4ebacea9 Mon Sep 17 00:00:00 2001 From: shingha Date: Fri, 13 May 2022 14:47:46 +0900 Subject: [PATCH 35/57] =?UTF-8?q?[STAR-13]=20feature:=20=EC=9D=B4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EB=8C=80=20=EC=B6=94=EC=B2=9C=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 2 +- .../NSMutableAttributedString+Extension.swift | 18 +++++++++-- .../Present/Home/HomeViewController.swift | 29 +++++++++++------- .../Sources/Present/Home/HomeViewModel.swift | 30 +++++++++++++------ .../Recommand/RecommandMenuCellView.swift | 4 +-- .../Recommand/RecommandMenuDataSource.swift | 16 +++++++++- .../RecommandMenuViewController.swift | 9 ++++-- .../Recommand/RecommandMenuViewModel.swift | 10 ------- .../OrderDetailViewController.swift | 28 +++++++++++++++-- 9 files changed, 105 insertions(+), 41 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index 06be425..a07a3d7 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -395,8 +395,8 @@ children = ( E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */, E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */, - E07233D8282BD60200AF3E16 /* RecommandMenuViewController.swift */, E07233EB282D0BE500AF3E16 /* RecommandMenuViewModel.swift */, + E07233D8282BD60200AF3E16 /* RecommandMenuViewController.swift */, ); path = Recommand; sourceTree = ""; diff --git a/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift index 3d72f9c..8389c12 100644 --- a/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift +++ b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift @@ -13,10 +13,11 @@ enum AttributedStringOption { case background(color: UIColor) case underLined case strikethrough + case paragraphStyle(_ style: NSMutableParagraphStyle) } -extension NSMutableAttributedString { - func addString(_ string: String, options: [AttributedStringOption] = []) -> NSMutableAttributedString { +extension NSAttributedString { + static func create(_ string: String, options: [AttributedStringOption] = []) -> NSAttributedString { var attributes = [NSAttributedString.Key: Any]() options.forEach { option in switch option { @@ -30,9 +31,20 @@ extension NSMutableAttributedString { attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue case .strikethrough: attributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue + case .paragraphStyle(let style): + attributes[.paragraphStyle] = style } } - self.append(NSAttributedString(string: string, attributes: attributes)) + return NSAttributedString(string: string, attributes: attributes) + } +} + +extension NSMutableAttributedString { + + func addStrings(_ strings: [NSAttributedString]) -> NSMutableAttributedString { + strings.forEach { + self.append($0) + } return self } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 5014b09..d73362a 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -38,8 +38,13 @@ class HomeViewController: UIViewController { return view }() - private lazy var recommandMenuViewController: RecommandMenuViewController = { - let recommandMenuView = RecommandMenuViewController(viewModel: self.viewModel.recommandMenuViewModel) + private lazy var myRecommandMenuViewController: RecommandMenuViewController = { + let recommandMenuView = RecommandMenuViewController(type: .myMenu, viewModel: self.viewModel.recommandMenuViewModel) + return recommandMenuView + }() + + private lazy var timeRecommandMenuViewController: RecommandMenuViewController = { + let recommandMenuView = RecommandMenuViewController(type: .thisTime, viewModel: self.viewModel.timeRecommandMenuViewModel) return recommandMenuView }() @@ -97,6 +102,16 @@ class HomeViewController: UIViewController { vc.homeInfoView.setMessage(message) }) .disposed(by: disposeBag) + + viewModel.state().presentProductDetailView + .withUnretained(self) + .bind(onNext: { vc, product in + //TODO: Present Detail View + let viewController = OrderDetailViewController() + viewController.modalPresentationStyle = .fullScreen + vc.navigationController?.pushViewController(viewController, animated: true) + }) + .disposed(by: disposeBag) } private func attribute() { @@ -107,9 +122,10 @@ class HomeViewController: UIViewController { view.addSubview(scrollView) view.addSubview(homeInfoView) scrollView.addSubview(contentStackView) - contentStackView.addArrangedSubview(recommandMenuViewController.view) + contentStackView.addArrangedSubview(myRecommandMenuViewController.view) contentStackView.addArrangedSubview(mainEventViewController.view) contentStackView.addArrangedSubview(whatsNewViewController.view) + contentStackView.addArrangedSubview(timeRecommandMenuViewController.view) contentStackView.setCustomSpacing(Constants.stickyViewHeight, after: homeInfoView) @@ -133,13 +149,6 @@ class HomeViewController: UIViewController { $0.leading.trailing.equalToSuperview() $0.height.equalTo(Constants.homeInfoViewHeight) } - - viewModel.state().presentProductDetailView - .withUnretained(self) - .bind(onNext: { vc, product in - //TODO: Present Detail View - }) - .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index ca655d9..9113ac8 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -27,6 +27,7 @@ protocol HomeViewModelProperty { var whatsNewViewModel: WhatsNewViewModelProtocol { get } var mainEventViewModel: MainEventViewModelProtocol { get } var recommandMenuViewModel: RecommandMenuViewModelProtocol { get } + var timeRecommandMenuViewModel: RecommandMenuViewModelProtocol { get } } typealias HomeViewModelProtocol = HomeViewModelBinding & HomeViewModelProperty @@ -44,7 +45,8 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA let whatsNewViewModel: WhatsNewViewModelProtocol = WhatsNewViewModel() let mainEventViewModel: MainEventViewModelProtocol = MainEventViewModel() let recommandMenuViewModel: RecommandMenuViewModelProtocol = RecommandMenuViewModel() - + let timeRecommandMenuViewModel: RecommandMenuViewModelProtocol = RecommandMenuViewModel() + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() @@ -76,10 +78,22 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA .withUnretained(self) .bind(onNext: { model, name in model.titleMessage.accept("\(name)님\n오늘 하루도 고생 많으셨어요!") - model.recommandMenuViewModel.action().loadedUserName.accept(name) + let myRecommandTitle = NSMutableAttributedString().addStrings([ + .create(name, options: [.foreground(color: .brown1)]), + .create("님을 위한 추천 메뉴") + ]) + model.recommandMenuViewModel.state().displayTitle.accept(myRecommandTitle) + + let timeRecommandtitle = NSMutableAttributedString(string: "이 시간대 추천 메뉴") + model.timeRecommandMenuViewModel.state().displayTitle.accept(timeRecommandtitle) }) .disposed(by: disposeBag) + requestHome + .compactMap { $0.value?.nowRecommand.products } + .bind(to: timeRecommandMenuViewModel.action().loadedProducts) + .disposed(by: disposeBag) + requestHome .compactMap { $0.value?.mainEvent } .bind(to: mainEventViewModel.action().loadedEvent) @@ -93,16 +107,14 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA }) .disposed(by: disposeBag) - recommandMenuViewModel.action().selectedProduct + Observable + .merge( + recommandMenuViewModel.action().selectedProduct.asObservable(), + timeRecommandMenuViewModel.action().selectedProduct.asObservable() + ) .withUnretained(self) .compactMap { model, index in model.homeData?.yourRecommand.products?[index] } .bind(to: presentProductDetailView) .disposed(by: disposeBag) - - mainEventViewModel.action().tappedEvent - .bind(onNext: { - //TODO: Tapped Main Event - }) - .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift index a2a444a..17ba118 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift @@ -56,8 +56,8 @@ class RecommandMenuCellView: UICollectionViewCell { } } - func setName(_ name: String) { - nameLabel.text = name + func setName(_ name: NSMutableAttributedString) { + nameLabel.attributedText = name } func setThumbnail(_ imageUrl: URL?) { diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift index 7cb1e9c..03726f6 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift @@ -12,6 +12,12 @@ class RecommandMenuDataSource: NSObject, UICollectionViewDataSource { private var products: [StarbucksEntity.ProductInfo] = [] private var productimages: [[StarbucksEntity.ProductImageInfo]] = [] + private let type: RecommandMenuType + + init(type: RecommandMenuType) { + self.type = type + super.init() + } func updateProducts(_ products: [StarbucksEntity.ProductInfo]) { self.products = products @@ -34,7 +40,15 @@ class RecommandMenuDataSource: NSObject, UICollectionViewDataSource { let product = products[index] let image = index >= productimages.count ? nil : productimages[index].first - cell.setName(product.productName) + var attributedStrings: [NSAttributedString] = [] + if type == .thisTime { + let indexText: NSAttributedString = .create("\(index + 1)", options: [.font(.systemFont(ofSize: 20, weight: .bold))]) + attributedStrings.append(indexText) + } + attributedStrings.append(.create(product.productName)) + + let name = NSMutableAttributedString().addStrings(attributedStrings) + cell.setName(name) cell.setThumbnail(image?.imageUrl) return cell } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift index 50ca05a..86885a0 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift @@ -8,6 +8,10 @@ import RxSwift import UIKit +enum RecommandMenuType { + case myMenu, thisTime +} + class RecommandMenuViewController: UIViewController { enum Constants { static let cellSize = CGSize(width: 130, height: 180) @@ -35,10 +39,11 @@ class RecommandMenuViewController: UIViewController { private let viewModel: RecommandMenuViewModelProtocol private let disposeBag = DisposeBag() - private let dataSource = RecommandMenuDataSource() + private let dataSource: RecommandMenuDataSource - init(viewModel: RecommandMenuViewModelProtocol) { + init(type: RecommandMenuType, viewModel: RecommandMenuViewModelProtocol) { self.viewModel = viewModel + self.dataSource = RecommandMenuDataSource(type: type) super.init(nibName: nil, bundle: nil) bind() diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift index a8c3fe9..d1feec8 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift @@ -11,7 +11,6 @@ import RxSwift protocol RecommandMenuViewModelAction { var loadedProducts: PublishRelay<[String]> { get } - var loadedUserName: PublishRelay { get } var selectedProduct: PublishRelay { get } } @@ -33,7 +32,6 @@ class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewMo func action() -> RecommandMenuViewModelAction { self } let loadedProducts = PublishRelay<[String]>() - let loadedUserName = PublishRelay() let selectedProduct = PublishRelay() func state() -> RecommandMenuViewModelState { self } @@ -67,13 +65,5 @@ class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewMo .map { $0.compactMap { $0.file }.filter { !$0.isEmpty } } .bind(to: loadedRecommandImage) .disposed(by: disposeBag) - - loadedUserName - .map { NSMutableAttributedString() - .addString($0, options: [.foreground(color: .brown1)]) - .addString("님을 위한 추천 메뉴") - } - .bind(to: displayTitle) - .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift index 36588dd..96b57e0 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift @@ -5,12 +5,34 @@ // Created by YEONGJIN JANG on 2022/05/10. // +import RxSwift import UIKit class OrderDetailViewController: UIViewController { + private let disposeBag = DisposeBag() - override func viewDidLoad() { - super.viewDidLoad() - + init() { + super.init(nibName: nil, bundle: nil) + bind() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + rx.viewDidAppear + .map { _ in } + .withUnretained(self) + .bind(onNext: { vc, _ in + vc.navigationController?.isNavigationBarHidden = false + }) + .disposed(by: disposeBag) + } + + private func layout() { + } } From 9a0fecc8f660bc054a60a82a088e6a28341236b1 Mon Sep 17 00:00:00 2001 From: shingha Date: Sun, 15 May 2022 00:05:25 +0900 Subject: [PATCH 36/57] =?UTF-8?q?[STAR-19]=20feature:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 36 ++++- .../Sources/API/StarbucksTarget.swift | 22 +-- .../Sources/Model/StarbucksEntity.swift | 30 ++++- .../Present/Home/HomeViewController.swift | 20 ++- .../Sources/Present/Home/HomeViewModel.swift | 6 + .../Home/View/HomeInfoView/HomeInfoView.swift | 13 +- .../Recommand/RecommandMenuViewModel.swift | 5 +- .../View/WhatsNew/WhatsNewDataSource.swift | 4 +- .../WhatsNew/WhatsNewViewController.swift | 4 + .../View/WhatsNew/WhatsNewViewModel.swift | 7 +- .../OrderDetailViewController.swift | 5 + .../View/WhatsNewListViewCell.swift | 125 ++++++++++++++++++ .../WhatsNewList/WhatsNewListDataSource.swift | 32 +++++ .../WhatsNewListViewController.swift | 74 +++++++++++ .../WhatsNewList/WhatsNewListViewModel.swift | 55 ++++++++ .../Starbucks/StarbucksRepository.swift | 4 +- .../Starbucks/StarbucksRepositoryImpl.swift | 18 ++- 17 files changed, 433 insertions(+), 27 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Present/WhatsNewList/View/WhatsNewListViewCell.swift create mode 100644 Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListDataSource.swift create mode 100644 Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift create mode 100644 Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index a07a3d7..f4b9110 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; + E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0039B55282FD92800298719 /* WhatsNewListViewController.swift */; }; + E0039B58282FD93200298719 /* WhatsNewListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0039B57282FD93200298719 /* WhatsNewListViewModel.swift */; }; + E0108CD4282FEE4D00CC736C /* WhatsNewListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CD3282FEE4D00CC736C /* WhatsNewListDataSource.swift */; }; + E0108CD6282FEF4A00CC736C /* WhatsNewListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CD5282FEF4A00CC736C /* WhatsNewListViewCell.swift */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; E0723058282A13C900AF3E16 /* StarbuckTargetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */; }; E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723381282B7DB900AF3E16 /* HomeViewModel.swift */; }; @@ -76,6 +80,10 @@ 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 879C2C634B79C5B7E7318DE8 /* Pods-Starbucks.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Starbucks.debug.xcconfig"; path = "Target Support Files/Pods-Starbucks/Pods-Starbucks.debug.xcconfig"; sourceTree = ""; }; + E0039B55282FD92800298719 /* WhatsNewListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewListViewController.swift; sourceTree = ""; }; + E0039B57282FD93200298719 /* WhatsNewListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewListViewModel.swift; sourceTree = ""; }; + E0108CD3282FEE4D00CC736C /* WhatsNewListDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewListDataSource.swift; sourceTree = ""; }; + E0108CD5282FEF4A00CC736C /* WhatsNewListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewListViewCell.swift; sourceTree = ""; }; E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -162,6 +170,25 @@ path = Pods; sourceTree = ""; }; + E0039B54282FD8C000298719 /* WhatsNewList */ = { + isa = PBXGroup; + children = ( + E0039B59282FE27F00298719 /* View */, + E0108CD3282FEE4D00CC736C /* WhatsNewListDataSource.swift */, + E0039B57282FD93200298719 /* WhatsNewListViewModel.swift */, + E0039B55282FD92800298719 /* WhatsNewListViewController.swift */, + ); + path = WhatsNewList; + sourceTree = ""; + }; + E0039B59282FE27F00298719 /* View */ = { + isa = PBXGroup; + children = ( + E0108CD5282FEF4A00CC736C /* WhatsNewListViewCell.swift */, + ); + path = View; + sourceTree = ""; + }; E01B526D28289D17009918AE = { isa = PBXGroup; children = ( @@ -235,10 +262,11 @@ E072337F282B7DB900AF3E16 /* Present */ = { isa = PBXGroup; children = ( + E0723386282B7DB900AF3E16 /* TabBar */, E0723380282B7DB900AF3E16 /* Home */, - E0723383282B7DB900AF3E16 /* RootWindow.swift */, + E0039B54282FD8C000298719 /* WhatsNewList */, E0723384282B7DB900AF3E16 /* Pay */, - E0723386282B7DB900AF3E16 /* TabBar */, + E0723383282B7DB900AF3E16 /* RootWindow.swift */, E0723388282B7DB900AF3E16 /* OrderDetail */, E072338A282B7DB900AF3E16 /* OrderList */, E072338C282B7DB900AF3E16 /* OrderCategory */, @@ -661,6 +689,7 @@ files = ( E07233E3282CA71B00AF3E16 /* MainEventViewController.swift in Sources */, E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */, + E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */, E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */, E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */, @@ -689,11 +718,13 @@ E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */, E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, E07233EE282D0F9300AF3E16 /* WhatsNewDataSource.swift in Sources */, + E0108CD4282FEE4D00CC736C /* WhatsNewListDataSource.swift in Sources */, E07233E6282D039600AF3E16 /* WhatsNewViewController.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, E07233F2282D235100AF3E16 /* NSMutableAttributedString+Extension.swift in Sources */, E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */, E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */, + E0039B58282FD93200298719 /* WhatsNewListViewModel.swift in Sources */, E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */, E07233B4282B7DB900AF3E16 /* CategoryTableViewCell.swift in Sources */, E07233E8282D03A000AF3E16 /* WhatsNewViewModel.swift in Sources */, @@ -703,6 +734,7 @@ E07233AD282B7DB900AF3E16 /* PayViewController.swift in Sources */, E07233B3282B7DB900AF3E16 /* OrderCategoryViewController.swift in Sources */, E07233B6282B7DB900AF3E16 /* StarbucksRepository.swift in Sources */, + E0108CD6282FEF4A00CC736C /* WhatsNewListViewCell.swift in Sources */, E07233BF282B7DB900AF3E16 /* APIError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift index b9e0fb9..5ed9a78 100644 --- a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -9,7 +9,9 @@ import Foundation enum StarbucksTarget: BaseTarget { case requestHome - case requestEvent + case requestPromotion + case requestNews + case requestNotice case requestProductInfo(_ id: String) case requestProductImage(_ id: String) } @@ -20,7 +22,7 @@ extension StarbucksTarget { switch self { case .requestHome: return URL(string: "https://api.codesquad.kr/starbuckst") - case .requestEvent, .requestProductInfo, .requestProductImage: + case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice: return URL(string: "https://www.starbucks.co.kr") } } @@ -29,20 +31,24 @@ extension StarbucksTarget { switch self { case .requestHome: return nil - case .requestEvent: + case .requestPromotion: return "/whats_new/getIngList.do" case .requestProductInfo: return "/menu/productViewAjax.do" case .requestProductImage: return "/menu/productFileAjax.do" + case .requestNews: + return "/whats_new/newsListAjax.do" + case .requestNotice: + return "/whats_new/noticeListAjax.do" } } var parameter: [String: Any]? { switch self { - case .requestHome: + case .requestHome, .requestNews, .requestNotice: return nil - case .requestEvent: + case .requestPromotion: return ["MENU_CD": "all"] case .requestProductInfo(let id): return ["product_cd": id] @@ -53,9 +59,9 @@ extension StarbucksTarget { var method: HTTPMethod { switch self { - case .requestHome: + case .requestHome, .requestNews, .requestNotice: return .get - case .requestEvent, .requestProductInfo, .requestProductImage: + case .requestPromotion, .requestProductInfo, .requestProductImage: return .post } } @@ -64,7 +70,7 @@ extension StarbucksTarget { switch self { case .requestHome: return .json - case .requestEvent, .requestProductInfo, .requestProductImage: + case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice: return .urlencode } } diff --git a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift index c039d78..9209560 100644 --- a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -40,11 +40,11 @@ extension StarbucksEntity { } extension StarbucksEntity { - struct HomeEvent: Decodable { - let list: [Event] + struct Promotions: Decodable { + let list: [Promotion] } - struct Event: Decodable { + struct Promotion: Decodable { let title: String let imageUploadPath: URL let thumbnail: String @@ -57,6 +57,30 @@ extension StarbucksEntity { } } +extension StarbucksEntity { + struct Events: Decodable { + let list: [Event] + } + + struct Event: Decodable { + let title: String + let newsDate: String + let thumbnail: String + let startDate: String + + enum CodingKeys: String, CodingKey { + case title + case newsDate = "news_dt" + case thumbnail = "img_nm" + case startDate = "start_dt" + } + + var thumbnailUrl: URL? { + URL(string: "https://image.istarbucks.co.kr/upload/news/\(thumbnail)") + } + } +} + extension StarbucksEntity { struct ProductDetail: Decodable { let view: ProductInfo? diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index d73362a..4ba0ac5 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -84,7 +84,9 @@ class HomeViewController: UIViewController { .map { _ in } .withUnretained(self) .bind(onNext: { vc, _ in - vc.navigationController?.isNavigationBarHidden = true + vc.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) + vc.navigationController?.navigationBar.shadowImage = UIImage() + vc.navigationController?.navigationBar.backgroundColor = .clear }) .disposed(by: disposeBag) @@ -106,12 +108,24 @@ class HomeViewController: UIViewController { viewModel.state().presentProductDetailView .withUnretained(self) .bind(onNext: { vc, product in - //TODO: Present Detail View let viewController = OrderDetailViewController() - viewController.modalPresentationStyle = .fullScreen vc.navigationController?.pushViewController(viewController, animated: true) + vc.tabBarController?.tabBar.isHidden = true }) .disposed(by: disposeBag) + + Observable + .merge( + homeInfoView.tappedWhatsNewButton.asObservable(), + viewModel.state().presentWhatsNewListView.asObservable() + ) + .withUnretained(self) + .bind(onNext: { vc, _ in + let viewController = WhatsNewListViewController(viewModel: WhatsNewListViewModel()) + vc.navigationController?.pushViewController(viewController, animated: true) + }) + .disposed(by: disposeBag) + } private func attribute() { diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 9113ac8..823e372 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -16,6 +16,7 @@ protocol HomeViewModelAction { protocol HomeViewModelState { var titleMessage: PublishRelay { get } var presentProductDetailView: PublishRelay { get } + var presentWhatsNewListView: PublishRelay { get } } protocol HomeViewModelBinding { @@ -41,6 +42,7 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA let titleMessage = PublishRelay() let presentProductDetailView = PublishRelay() + let presentWhatsNewListView = PublishRelay() let whatsNewViewModel: WhatsNewViewModelProtocol = WhatsNewViewModel() let mainEventViewModel: MainEventViewModelProtocol = MainEventViewModel() @@ -116,5 +118,9 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA .compactMap { model, index in model.homeData?.yourRecommand.products?[index] } .bind(to: presentProductDetailView) .disposed(by: disposeBag) + + whatsNewViewModel.action().tappedSeeAllButton + .bind(to: presentWhatsNewListView) + .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift index f4e7f06..a510bf1 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift @@ -5,6 +5,8 @@ // Created by seongha shin on 2022/05/12. // +import RxSwift +import RxRelay import UIKit class HomeInfoView: UIView { @@ -59,11 +61,14 @@ class HomeInfoView: UIView { }() private let stickViewHeight: CGFloat + private let disposeBag = DisposeBag() + + let tappedWhatsNewButton = PublishRelay() init(stickViewHeight: CGFloat) { self.stickViewHeight = stickViewHeight super.init(frame: .zero) - + bind() attribute() layout() } @@ -73,6 +78,12 @@ class HomeInfoView: UIView { fatalError("init(coder:) has not been implemented") } + private func bind() { + whatsNewButton.rx.tap + .bind(to: tappedWhatsNewButton) + .disposed(by: disposeBag) + } + private func attribute() { backgroundColor = .white } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift index d1feec8..11070ef 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift @@ -45,9 +45,10 @@ class RecommandMenuViewModel: RecommandMenuViewModelBinding, RecommandMenuViewMo init() { loadedProducts - .flatMapLatest { ids in + .withUnretained(self) + .flatMapLatest { model, ids in Observable.zip( ids.map { id in - self.starbucksRepository.requestDetail(id).asObservable() + model.starbucksRepository.requestDetail(id).asObservable() .compactMap { $0.value } }) } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift index 371ca6a..60d1872 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift @@ -8,9 +8,9 @@ import UIKit class WhatsNewDataSource: NSObject, UICollectionViewDataSource { - private var events: [StarbucksEntity.Event] = [] + private var events: [StarbucksEntity.Promotion] = [] - func updateEvents(_ events: [StarbucksEntity.Event]) { + func updateEvents(_ events: [StarbucksEntity.Promotion]) { self.events = events } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift index 9d2f8bf..a24f8aa 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift @@ -72,6 +72,10 @@ class WhatsNewViewController: UIViewController { vc.eventCollectionView.reloadData() }) .disposed(by: disposeBag) + + seeAllButton.rx.tap + .bind(to: viewModel.action().tappedSeeAllButton) + .disposed(by: disposeBag) } private func attribute() { diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift index 6a50053..afb0daf 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift @@ -12,10 +12,11 @@ import RxSwift protocol WhatsNewViewModelAction { var loadEvent: PublishRelay { get } var selectedEvent: PublishRelay { get } + var tappedSeeAllButton: PublishRelay { get } } protocol WhatsNewViewModelState { - var loadedEvent: PublishRelay<[StarbucksEntity.Event]> { get } + var loadedEvent: PublishRelay<[StarbucksEntity.Promotion]> { get } } protocol WhatsNewViewModelBinding { @@ -26,14 +27,16 @@ protocol WhatsNewViewModelBinding { typealias WhatsNewViewModelProtocol = WhatsNewViewModelBinding class WhatsNewViewModel: WhatsNewViewModelBinding, WhatsNewViewModelAction, WhatsNewViewModelState { + func action() -> WhatsNewViewModelAction { self } let loadEvent = PublishRelay() let selectedEvent = PublishRelay() + let tappedSeeAllButton = PublishRelay() func state() -> WhatsNewViewModelState { self } - let loadedEvent = PublishRelay<[StarbucksEntity.Event]>() + let loadedEvent = PublishRelay<[StarbucksEntity.Promotion]>() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() diff --git a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift index 96b57e0..03f727b 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift @@ -14,6 +14,7 @@ class OrderDetailViewController: UIViewController { init() { super.init(nibName: nil, bundle: nil) bind() + attribute() layout() } @@ -32,6 +33,10 @@ class OrderDetailViewController: UIViewController { .disposed(by: disposeBag) } + private func attribute() { + view.backgroundColor = .white + } + private func layout() { } diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/View/WhatsNewListViewCell.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/View/WhatsNewListViewCell.swift new file mode 100644 index 0000000..0519d4c --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/View/WhatsNewListViewCell.swift @@ -0,0 +1,125 @@ +// +// WhatsNewListViewCell.swift +// Starbucks +// +// Created by seongha shin on 2022/05/14. +// + +import RxSwift +import UIKit + +class WhatsNewListViewCell: UITableViewCell { + + enum Constant { + static let imageWidth = 100.0 + static let imageAspect = 480.0 / 720.0 + } + + static let identifier = "WhatsNewListViewCell" + + private let thumbnail: UIImageView = { + let imageView = UIImageView() + imageView.backgroundColor = .gray.withAlphaComponent(0.5) + imageView.layer.cornerRadius = 8 + imageView.clipsToBounds = true + return imageView + }() + + private let infoStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 5 + return stackView + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15, weight: .regular) + label.textColor = .black + label.textAlignment = .left + label.lineBreakMode = .byCharWrapping + label.numberOfLines = 0 + return label + }() + + private let newsDate: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15) + label.textColor = .lightGray + label.textAlignment = .left + return label + }() + + @Inject(\.imageManager) private var imageManager: ImageManager + private let disposeBag = DisposeBag() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("Init with coder is unavailable") + } + + private func attribute() { + contentView.backgroundColor = .white + } + + private func layout() { + contentView.addSubview(thumbnail) + contentView.addSubview(infoStackView) + infoStackView.addArrangedSubview(titleLabel) + infoStackView.addArrangedSubview(newsDate) + + contentView.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(thumbnail).offset(20) + } + + thumbnail.snp.makeConstraints { + $0.top.equalToSuperview().offset(20) + $0.leading.equalToSuperview().offset(20) + $0.width.equalTo(Constant.imageWidth) + $0.height.equalTo(Constant.imageWidth * Constant.imageAspect) + } + + infoStackView.snp.makeConstraints { + $0.leading.equalTo(thumbnail.snp.trailing).offset(15) + $0.trailing.equalToSuperview().inset(15) + $0.centerY.equalTo(thumbnail) + } + } + + func setTitle(_ title: String) { + titleLabel.text = title + } + + func setDate(_ date: String) { + newsDate.text = date + } + + func setThumbnail(_ url: URL?) { + if let url = url { + thumbnail.isHidden = false + imageManager.loadImage(url: url).asObservable() + .withUnretained(self) + .observe(on: MainScheduler.asyncInstance) + .bind(onNext: { cell, image in + cell.thumbnail.image = image + }) + .disposed(by: disposeBag) + thumbnail.snp.updateConstraints { + $0.leading.equalToSuperview().offset(20) + $0.width.equalTo(Constant.imageWidth) + } + } else { + thumbnail.isHidden = true + thumbnail.snp.updateConstraints { + $0.leading.equalToSuperview().offset(0) + $0.width.equalTo(0) + } + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListDataSource.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListDataSource.swift new file mode 100644 index 0000000..29d7b04 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListDataSource.swift @@ -0,0 +1,32 @@ +// +// WhatsNewListDataSource.swift +// Starbucks +// +// Created by seongha shin on 2022/05/14. +// + +import UIKit + +class WhatsNewListDataSource: NSObject, UITableViewDataSource { + private var events: [StarbucksEntity.Event] = [] + + func updateEvents(_ events: [StarbucksEntity.Event]) { + self.events = events + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + events.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: WhatsNewListViewCell.identifier, for: indexPath) as? WhatsNewListViewCell else { + return UITableViewCell() + } + + let event = events[indexPath.item] + cell.setTitle(event.title) + cell.setDate(event.newsDate) + cell.setThumbnail(event.thumbnail.isEmpty ? nil : event.thumbnailUrl) + return cell + } +} diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift new file mode 100644 index 0000000..d841693 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift @@ -0,0 +1,74 @@ +// +// WhatsNewListViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/14. +// + +import RxSwift +import UIKit + +class WhatsNewListViewController: UIViewController { + + private let tableView: UITableView = { + let tableView = UITableView() + tableView.register(WhatsNewListViewCell.self, forCellReuseIdentifier: WhatsNewListViewCell.identifier) + tableView.estimatedRowHeight = 50 + return tableView + }() + + private let viewModel: WhatsNewListViewProtocol + private let disposeBag = DisposeBag() + private let dataSource = WhatsNewListDataSource() + + init(viewModel: WhatsNewListViewProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + bind() + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + + rx.viewDidLoad + .bind(to: viewModel.action().loadEvents) + .disposed(by: disposeBag) + + rx.viewDidAppear + .map { _ in } + .withUnretained(self) + .bind(onNext: { vc, _ in + vc.navigationController?.isNavigationBarHidden = false + }) + .disposed(by: disposeBag) + + viewModel.state().loadedEvents + .withUnretained(self) + .bind(onNext: { vc, events in + vc.dataSource.updateEvents(events) + vc.tableView.reloadData() + }) + .disposed(by: disposeBag) + } + + private func attribute() { + hidesBottomBarWhenPushed = true + view.backgroundColor = .white + + tableView.dataSource = dataSource + } + + private func layout() { + view.addSubview(tableView) + + tableView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift new file mode 100644 index 0000000..3bc5fd3 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift @@ -0,0 +1,55 @@ +// +// WhatsNewListViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/14. +// + +import Foundation +import RxRelay +import RxSwift + +protocol WhatsNewListViewAction { + var loadEvents: PublishRelay { get } +} + +protocol WhatsNewListViewState { + var loadedEvents: PublishRelay<[StarbucksEntity.Event]> { get } +} + +protocol WhatsNewListViewBinding { + func action() -> WhatsNewListViewAction + func state() -> WhatsNewListViewState +} + +typealias WhatsNewListViewProtocol = WhatsNewListViewBinding + +class WhatsNewListViewModel: WhatsNewListViewProtocol, WhatsNewListViewAction, WhatsNewListViewState { + func action() -> WhatsNewListViewAction { self } + + let loadEvents = PublishRelay() + + func state() -> WhatsNewListViewState { self } + + let loadedEvents = PublishRelay<[StarbucksEntity.Event]>() + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + private let events: [StarbucksEntity.Event] = [] + + init() { + loadEvents + .withUnretained(self) + .flatMapLatest { model, _ in + Observable.merge( + model.starbucksRepository.requestNews().asObservable().compactMap { $0.value?.list }, + model.starbucksRepository.requestNotice().asObservable().compactMap { $0.value?.list } + ) + } + .scan(self.events, accumulator: +) + .map { $0.sorted(by: { lhs, rhs in lhs.startDate > rhs.startDate }) } + .bind(to: loadedEvents) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift index ebdc368..8339366 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -10,8 +10,10 @@ import RxSwift protocol StarbucksRepository { func requestHome() -> Single> - func requestEvent() -> Single> + func requestEvent() -> Single> func requestDetail(_ id: String) -> Single> func requestDetailImage(_ id: String) -> Single> func requestCategory() -> Single> + func requestNews() -> Single> + func requestNotice() -> Single> } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift index 0500f80..d2dc6d9 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -15,10 +15,10 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo .map(StarbucksEntity.Home.self) } - func requestEvent() -> Single> { + func requestEvent() -> Single> { provider - .request(.requestEvent) - .map(StarbucksEntity.HomeEvent.self) + .request(.requestPromotion) + .map(StarbucksEntity.Promotions.self) } func requestDetail(_ id: String) -> Single> { @@ -49,4 +49,16 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo return Disposables.create { } } } + + func requestNews() -> Single> { + provider + .request(.requestNews) + .map(StarbucksEntity.Events.self) + } + + func requestNotice() -> Single> { + provider + .request(.requestNotice) + .map(StarbucksEntity.Events.self) + } } From 12b248042fd9ba5402f52b3d9bba09a34677f843 Mon Sep 17 00:00:00 2001 From: shingha Date: Sun, 15 May 2022 21:18:44 +0900 Subject: [PATCH 37/57] =?UTF-8?q?[STAR-19]=20feature:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mockImage.imageset/Contents.json | 21 ------------------ .../mockImage.imageset/mockImage.png | Bin 122768 -> 0 bytes .../Present/Home/HomeViewController.swift | 10 +++++++-- .../MainEvent/MainEventViewController.swift | 2 +- .../Recommand/RecommandMenuCellView.swift | 5 +++++ .../Recommand/RecommandMenuDataSource.swift | 9 ++++++-- .../RecommandMenuViewController.swift | 4 +++- .../WhatsNewListViewController.swift | 21 +++++++++++------- 8 files changed, 37 insertions(+), 35 deletions(-) delete mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json delete mode 100644 Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/mockImage.png diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json b/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json deleted file mode 100644 index 4c27d3e..0000000 --- a/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "mockImage.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/mockImage.png b/Starbucks/Starbucks/Resource/Assets.xcassets/mockImage.imageset/mockImage.png deleted file mode 100644 index 98b8a88db20979cbccb470c41d1ce0dac9d5ce6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122768 zcmZ5{byQT*yEYOct&|`sDcubN2#Vw&Al;3^49yS&0!nxHAgy$FH%NDPGc-dCFrUBs z-F5G`zJJczZ=7eZ_c`aR_3U@=6RNJJK!E!O7Yz-KK=G5j1{&Hk-hboc7tjA)FCFhm z{y7Tmv~*o{RaL|-zz*DImSA%$Zis{9zc?D2q%_3Q%;KAsE4{gujU7me;k3D(f!@wi zia|$6l~>hK&dS#Alb5rVrk9$Qh1WL=F-rz%0InoN{2zdWm8%&&#K9iqA`X#a_%B@X zf8+nSc^K&bE5-Gj6oam+I=vj&*@|A6o0prH0f0*{>1=5&t|9;Nzi<9$Nio>Ex;l#U z@OXH5aC-=FgPm=7_{7A-czF4F`1!g1WpKH8f?UlYTp$<5|3Lg-40$UT3uik=S359> z{y&&z=3qBhDF%jrar*xTw}ROHFFMHOzsvte1kZmWJbc`|JpYycA4~j;i*Xk-Tx;lz$?hW^Phrw{=4FT z)bacecmIt4Vc!b$k8WO-#ntiq|9m zzJ@9k(SN`H(u*YT1!fGf7utc5)_dYl@7^-~u-55$Mg3V`z9M2jJ0u{Nk={a9zLFrU zm1uxIyCe{ciXDleoRTWl?2X8}bZ%c>_868x4L3@oC74F(W<29~7Q;3aD}U?Fx4B6| z(XfX`kl@eYh+aosru7#W5AS)%-~IL>WHVaYbbSen#Ut_*^+gNf!}c2O-K!1Dtc3JX zM~SMBjpmAkXhpdpE4(}kl7scH3KV0|U(u#*Puo554-RTZXs2C-hMj$!e}N{M+8vvR zE>kCa9AOAzCl00#s)!6>C4Jk1Yvxh*y<3iA5O3Gg+FJGEMVEC*I|UjpftmFM%)H=< z*|$m(5FF3GchFXVdCHg&CLoH1+M9R)5B`lA+2)lc*Q3V?k*N`P_Pv>^4%p^YBE-ow zdkY@&3&X1VzE8QU)JkHV(tnN7G_h&G!Xp?s#VVN6-W-0^t%2SMi*3gz6_fXFF#?mI+r$Xs-z*X;pE8rvU z!bPiCKoOefX~x3+9mA}>id{yFFzs5u$1JP<3D4+x+#a#bkHN<~$|vV$K@p8M5o}LU zo137)G2TcL3gK>gid&55FVFz==o6LnK4Dxgw|8RkBM_TVJfop_Pl2YvVAek zme_o6JqBLm1~Ftq-r*?yl#jvJe_r}i>6dfSn?<}CIcZteUoPWTMc7_w*RtK?RQm!E z0fW2*v;L~m{lRm?n+bm-3w>`fp(Tjn@YDSRc6RQBp{uYL zmKS5w8|cfGh!gp>_cy;3Cf0aKBZT{iw*t&8HhF)iNUHD^5NigD`8R%V45aL6={K)* zEn%C*q52-#UtpGOE@JLsmRUBGJ98g2-X3c ziV&$5npGm$_tzV9>m297XEtXd=Yh9Q0mPjt>+fGMpS{5P!QbWko@ztf>^rZT7E2oO zO33FP!OgGcWRNP7P(MO~5&CxLp1MM-K$a))gv7yV18zJF1e&5c9`*$Lg){07}h z^-7KNhE2ELSIhx%p>g%Z3U)s=A}1B|y_GD6w5$wv)OS>NB8P$#csPiDuWPAj`Fw$} ze_%&yjTT>Ne%6xvBa*0Wq`U%bm7&Qtvnvo3=8Du-l6Zeq5c%g%(WqwR=MkOK&r+p} z$_f*z`}SVXTO)iUe3PH_R23rg`*fDFXN%l5;eky_O@-nnt@5pkk5TtF1VKUNADjBf zBgrFK(`;4oMjS^ZM^@5a3o6vu>X+#omJ*j*=o8hNIpD!x!1EW8HRX2E2m6P&MyB)SEGlmrbYa^5%?d;a^v~_l$X+F^GvN3ua!!J zOuxQ#sOZZcTn~La5o4EPu4^`LCi*qcrl!L-^>g5aoJ9cNl&)#ZO2hDNVCZkfl0mO{ z^7yx!Zkly=zw81JECj+*gZ1k4N*5elO2nolSA3H12=L~+I^Uaqws2Y@u5+y+)jz2^ zP7O@ml2i~ZbRU)Cs3IF?pZnAKXDkgwW8d1>)O$Z;nA?csQvVm8h8*eZWa+kUA7L~g zTasB4s&l9VA9EfzxC^=0!`TqN2oAUb++?geqaRl9y$Z$t(~TZ{O-Ms%LX%0&92FVW zt@cq(QB5NAgwO6T@9xK4W}ArWZ@~c#q=NqpJ$s+*MBejPQvxuemw6~#c_p6SLKG;hS0_s zqdjBN2iFf8jLGt}VLo&_WM9ay-z{?LldrtWrHH4{q`Q1O%UUN{CjtU3Uo;e-r=54* z&SDzAS9x#LHUEC?=Mp&~lQ#C7u(>uS3o!+!?0dkmePQ#DtZ3r<7oA819Pgx(cLLWbBkGPnE5_uh;VQmX zG;pyU$aYn=W-(wr8g?5i9ehlA9q%D&B4(1{SK5(dD%WZH%VZ;3c|y5SSt99x$Dp+Y zEcWegm25n0n~a8oK!?1PT6d@YaQ*A%;nhrgS*4z4+wRKZe67O=@ejpe_j7mU-`;7z zOCnt}IBoJB^VmQAvE~M@SgWw;t|vAgOw>r99Gc{zYNRUn0VAO^HzesA!fwf8N$}N( z70yK;kMRDaz@4F;pF58Ruk9VmOtliUT9)c8mv)9c6TRz5>WAxYw6Sz*Pk0wAJ^Pct z%3f!_DNifNT$B(wey9kqKiUP2twegeA`(vS9s(^XQ5K`;^W@Do2E9cn$X9$a5*ai$G{S^TgS}}Dg~dY8ex5*6Nem;L|QBb zD+QB>gsZ6)3)z10D1m5^C;NxXQIcsA<`H!l*-P8-?EHG6w%R?*^}#6NxjK?|U6k{B z!qe}xsXv9oqc6uuGgCzkAnqkb%K&HYYeT>OxG06at-ugJ!A=pNP)7NcU+D( z9T4zhkM}26%aLu~DC5o6Rb&fNj)Z@@DcDV=qemsee?* zy4?4v`!f1lu2U{XIdeJds=ZI~IijQ4^HjXAt1qi<%t!j^*LH4Se;_Oc)&%JHrKKK2 z1|T)AXCA&S)n|jFS@~I6dZ=-1zLLtk#Zyy!wm$OA5)g&`-3tS4v+Ll_ODX%nBY+JZ z6#^!{%Uc`zPpS!h9F;{_$w&jjS8Y?75{=pM{e$}|`iFuOLyZJ!Z(z2W11|v1vreWm4Oy$oGuA>43v=3hWMHSMb%KTdb z+V}m?l~O5Ou6wU~(a>bj6y;^LAkQpY@7y_6P=;W93>_4e^epSk_XD$3R?-&|{ag7&M_$fm!*Vjw8Um)tZu<#RI1@-;l zGr%FMP2m~A>7Z{*_9H$=r?jUTlAX~5*B!axDxo>X1~-LebhzM*E-*eofXf0-WAkS5 zu=_!=lzp9hMD4usX3Fc}4?) zQ7HiVBtDOfi~T9729KqwEjfp%`6&_y1a_)|B~WdjNbn$$(r5<=Jgrwi{~hZsuLhls zg^E9$+ZS!zDI(lIAX7$0@>)4yoSxIFKIhgV+7JZwr{CPS9oBcw3<7W+Ee<_ zMHg*}dZSI6m#26fz=51Z2yk^TYQDcX{XMgoC(D9T!sY4ho@5gm~hWd2S%JuiLH{y%{^;$sQ`9->c()|Z7_{* z0-Cu#%=kTTm$Z2&-W`o?e*FW15x;x&j5C2E{>ScomwLv6^>GD>@tcawPVX?-Xqenk z)H(dl9nyk%6C~;*qwu!f?k2S_z~v%@U~xKwH%lGU!CRg@oqHx19>vG7%kbDyA+bz) z%Q(s=RVC=u zN#R7TP8AqIjN+E?Pp8~D=5Gk}NeUe#%B%cy0Va&$3hiDW+r@)`5eMpA`~>Y! zF$umt`Csc^R@7q3jRdjagMs0L-=WS7Y~@^P)*E4YvY(5#Nlz~PZd(?1d&t45vDS8@ zoGhJd`*VI#zYr8=EP{-4US48{tUFtJ;-Ui}Izb08SL2)8mcu|5Ufa{5*SBAcoF$jo zKj=FJl&NuN)M#zuyrUAuFPhiFEP+wSR+fq?Rzg32$2syA)`-DKwc7|uX|gEwT(p;- zdn(ePDIM^PeGu;9p)erCpY-*51P(PbO8E06~PL5J@q5K<_ zSAC&!n)(@@GqQ^IO{@3ZB7&A~o8urX|f8rkgf5}`MDuHh0` zvj$>ltd&TXf3k{jr`8qF%poS0at{42hM2XWTLA}pGVAp{o1E!e3vB|9?W9|ZcNkgT2&$^b&z5a zr0T3CDplWh(>$HD5#lJKv&z?<%j-p5@wy-3-F*|xAy+Hr9*UN57`KJRr;>Ct<=Pj& zQoc2h8@#YW;Iiq^TI6)~f#}r6M$bN|wyN^s3Dh*mm8JMIDp;6@%BuV@qPMKBqt`c( z3DiW-A*vRSH``A;%IQ%}U%|)g@0qYo{9x2PhECg_j;10URo-&%ON>`CHomaiklD#7 z)}GBu$y+M&#O`K1KqW5_Obovd>y;B*{ZSyk-81d0qZE{^=t0abh;3O@|AM{Q^v~p_OoyM z%|=S|EZ@Rx=dY*e?rML|MWo?)A1A6Bv5X5M$(_s?_*o^WVk#Fr9xW@>|F+9J+Z-*@ zSUn_}+SWwcZ-J@b!nmlf_!7{kaL)FXI@?0XcogpDaNl#9I4Gk+Z^YWdYDAfSetWOc zJ|8ucqD?xBO=-tiNT#~)%$cwH5N$uG8&`4Y7Lk&8UoX$oI$Jq1n7KkrJoILjIL1@U zHrI-U?Z&zwbqVA7Zwo2gp#jWDkH5z0^6bWcI(<~0*LgG^G`tj;P8?QPkMe`h^C1%_B)&|6X^TcvUn)%%6C3NJ4{bBGRhU4 zTJd}Po6aLBM>I<8SsUe1rRo4__x%f$;UfQSCRB@B;*=~HYU{$I;zkmM!MVIzmXq*J zDBmvCQrlFHs$SJ0CU1m(-rAEuWV4CslFfyDJHM>F9_N#5;?o>G6@;*kh2ndQ4>mWr z>DcXiw+8p4KK`qPQSwY{eBOKXa2vvODk>|iQ~q&&zx9R^HSj%i_Y2iuqtt)cWuqX% zR@+x5xoNta5bdpzrw_TMc`{5^fzBO3>7Eb4gAKkw6M~OcO7eGK3_7Qn!k&Lhc2;wK zH_-)i6Zxw`!J4u4`)`Xr^Sfu|H-^@NiFI_;2knC+fcgu;-!U0kEp%Agxr@hh^~b7e zPJ*GF@c_%(aOmM7ozb0~_5G4n>C-IgFqxK4@YgT)g9Vc#8;V*QUC(cIMrD~Hq`nDa z554>BL-K5ekJW-W+3)A|U-3rXnSkO?iK`c$mdG9Oxy2-%>`LwH-s;frV)*nyj-odx z$?p4*D)-AI1~)O1&+%8jM7r5$iUt(;k`@UAzLfR6-PvC{J4T%)YR?e8o$L6f+>jVE zLug{YXmxKPEwvytk|t6!_;Sw~PyBlj-XHNAx}?IB*w1x@QCEF3PN@roE>d<9BmNx} z)nPlDDgr+#W3U=+$6W6;eYex!Y?oW!IKH+z9atw^`w?fA&r+g{B`4m-AyG$}uxbjr z5cllKUuBQ*C5t#yVUS5?pVwFIRqmB%Y{jfNM(4nXOmCR~9n^cNzDhEjVK#2l6^oq> za!mfQzSXq$JYK^nsb9u%GC%rW$@jp7kX-sP+$Pf7de(c9M%RI4Y%tBsHUe<-$)-ne zUY@@&P9Ea@?j_Aqjx1^iXlcY&ky;di7wP_E`RZ1q`OJQ4OP*3RV_V^IorONk6|R*$ ztH1VHp}j6`e4FY~F4@Cg9W@@tG*8p7ST%3{a?X$Vtw@V%1!WOz8V7X}UTOFxB65(9 zwih1V485MAi(ekryDUa{ZmsH`^*KM;Ja8tDU(h|&EPdfxbEvQX6z^SYg)X1E;_JW` zEWyb_-oNuLy$IyNLasoF?>YfJp4GVOSR!z~clg(8;0V>T-YZx2j1PC3v^H4wCH6i z2xVn#6K6M9@n5paR)D;f+lyCK8Voa-&Y{D{5T`lt9~R!RB`FUf?cu}Mw3yHQMD1qM z7R%DEOcnt3Dbc3f)s@U%Yv5erR(@!^{Suc81p9Pw#Ux0*LV+{UlA38%P4JI3cww-uh7Sa1~#dD1QUu8kEv+ z1Cv4UCMW_zLIdwVRL z<%z_{>ET6kw z*>s=%&9A(jr(fafrpB^E`;*?S5zAAh*GxHmvcn|0YDuns={%bIwD$qcwK1Z`O~fEr zVff?P=&2KJ?)TR;7it$VgaLxAo)pxPGETL@a?v8w`)o17IX>L}H44Telw=dataTR_ z<@Yw4U59LvtaK^Ns?WxYYaH)1%o`h z=!c^555GSz^CeQ!{2>z{`Q>?O5})BYNxdepnD}MdlC>3Czi81S=Sblaf6D565dx1K z?uo?l-MQk8j8Iu0A=27xnq%1!+w}X$YDhU#N{HNRFt_e5hSn+CHf%V#EfmH~nz_6X zAf$|PAv9zsN#>qw5$uKQsZqwSj8<;hCOI%l)6j^p!Sq^z%^@IN$a2C=CP+W;??A6A zAFD9&x`+J7ehv5an!jHZJLhZ(0|597BJqO}>1)3{rkQX&W$?<~n=X}Thd=!fh zeiC>#P-wCcj{c{Mhvv|QL2u6Dj&mW^-snw^MUG|O8$HJz+T+-xnxmp`%BADbzrFkR z_8TI0Z1qbRh^LFFqy0JU_)`0tMY`K?XoZT`g`NLBtJz>++4%OWFA-8<(_B29_RPx_ zzX!s(XV~)nE^yj@y^%_9mF~8O#V0Nc_Nx^4&8W_?>H^T}htZ(9PP4@Nj4S>~r7SN}t+2kmH*SSt< z3))Ccp|-UAHI_P&1?eNEV{v1c-pQ#t@UM^639c&mQ^^Vl@;h>Rk93(-inTeZQv$HTI1C4GU^Ld6Bost~4Pt8wx zGiM9M0?BYjl}owo?2VtWyt=L;uSywMOYKB3-;U3g?t&1}z9!G&vB60m^kL1`UG2@B zCTpb{f1I9uAG<{3^0cD+*)pV))_!V+NJZvb^kT@Ein;IEFB%fon!=9x*?tn(Ju=$@ ziq>2vMEDcmZNIVi-WO0~0h;=aAI4BROoh!jhZ|7|E=90ye-{AL+x*ddMbfui`aMXxSl>3JW^%d z$-IYoPC^=xan)+)6I(Pl3mw@wsLN^fg=bl`EkCT^V~7jQ>l=Rew@G@MD5)1e&r5cP z(5bwL_y+A%xrSjtfadqNdrR<5$4Vn(!!s_teIB*CXgUCB&O@>^s-t<1r@s2g0r@xn zX-}v8lLn6>U~Xw-&!j%E^2SxMt8saBjA#7PuKCetglo7T4jsA0-IBo}QJLHVZYz}=A5v$rEXP`sv3^5PTl@u*Hw zS>t*C0dHEOV?8LnKbF*Q(Fe9TjjiBIvhe8=#J_Gj4#)WhiOJcjoj953AuH{XfsRp7 zyX9D4UB6}Z--#O?(BnC$oG+tLQ6HCdA)m(LvpRm&4`ejD^OWxP5|LFlOgeZS>ZlD` z?}}lYd^~$bB9X64Ff*YKQ4F$fq{Zf@K zFc0w7{2+PALX;1qOB_dKNmWKN2^U&;_RJU)zqV=*BaQa*zu3@Ga6C!v{1%{G3IBTb z&d5-pGfaJW$ST{*XV7L^y$+Za={cB3*PH(Ej%#l&Q;q)aC5ZUXJ@sp8GffmmXc@_y ze;=`=A3D&;-m7a)H)-zoRFluibw>Qr!@0S{4wdE}jJ7_!up4m*ABMww$$Wgo4iN%4 zhi1)>&h5~fO}e_`W|1YBAIrqbV}()cBi!Bd&KLP`1!O89T0-oMAk2}z$ldfc`_ppN zzejpa$G2{V+`aHvx(Qwb14Z`ceV(88A`Z4uEF@1VD$AGt-$T!6=sMyavV4C%I0Zd| z_E}9Dk=)SVN;5NjqSeymzydjEwfl3@!g5<){<>!}Ey!*bx|KRfS`j0nrA@L$)vn zMS7_X&v74lv5W4`fU@NPD%a- zZc1=rVgcT&;jjhjC90uwrdI^gN74%+H)<)m7EP3|2m)>T-Fb?kb7bP?63 zZD1yO8Ds(6Si8e@g)51qG;@)abVp~o4l^#7(PiFiXvdp1m{%sY*jDgd2YXy|n6($6 zNPFe1^AI;;!pH;z-|^Q1jQwL0O>kuXk||Mvd&0Vs=w?d73&AKT#riS#Gu6$7M*)C| zS2Y=>n_s@)JyTMhsG3VCQ(uFlea*<~-?=PD`c6mW`I|>fERxfWpu%oFnhaX*bBsc# zSwKDPw61xqj9P4P{Z@e`xCZ%+q#RnkS7O?>oH+ebzitCZ+mMKTBj@~Tr z4*Sa9P`pjFb@Ay0>9;;~^3~f7@vncK-oC_5R9;tqx*A(#{Qa><>Cj^dZ1PwLU+F#d zBwZgtApSYl{E)DE)I&6C%Dt_1=>(9)d%kAhj(R$+D<0;Eqva53x`i*D*3Ip$f#*)a zm8ZVV-llHuH@*^QH0u)Lj@};qVf)vJ^m-H=ala{Y_KJrn*|2I7)f^6|Lv6HMT;E7> z6uw@5BROicDlGYiNC{g5Et0PH~HE z+aYgb$;gTqnhms*nff)gb#I8;HCS<6avDUXm$*%`_X05gpcD;FK)$PO%TKtA-H6SL zUia$@p$DesZwy(9%f&jg~(CPZ1-%J7R5!DNSqgV)Fjs(?DW~_ad7i z9u;=wx=f_mmZ!~oCzc;Itwv!FAhpF`;dXZKFH5g?)yo(j`n6xzl%2}jA250_IJf=v_D@ga3{fSnL{H8A?c1a z{ei0d;ibMFOTBtN@kI$9wL(wl6m9YR60f#JlX=a<10j zCQgWMjUL;GY>%E$(H;nCb^K7pv%o?Nzm7-z(Zi=@$?~+@T*$<6^BQ7)jZ@(BvmIOx zwo?dt%o^NdS2kI7x!VXkjKFb*j66~vLrgE4#ffY3w!>1=IB>amZ65-pluIi|Qep!8 z`pYakHsemW?*kJ1w-7XgdrNd}m5fW$kL{>7nzkM1^ATnayts*-CHV2Kw>`_V+k6-8 zw}ngJx^@k;KT5~=N!!C3?3YI?P|TsXG+X`58;s)MvyJHXr$qVOXvKHIboJFDhH9-6 zOQVfGKAXhH9jevH^w&!xh28E?7cy)6Q|`l78w>8L&R3Vs@C~fwNSlP|$Pv0VFi!=F z+7Gb&vY1E{ps*+99v5EMji>$1gRYUucYd`+GzPOCK9b%He=b3HHnsDOcysEtrQ#o( z@p3&fXR$k1rN^JiH8n;Z!!goMM(<{kH)kuW@kL{e4wufztJuQlYHRCD>F~Lm)B5`I zhgsIof0K{WMP|<$Z+csXIJ5C_gF}CLL_V1%hGF2zmm?o9~i+J5ZR*?tg#U$eSyxal(S(y=U@0z+wlQxQWiXZLF-5&f4v2_?s;p2SA8q;WCn5Hp%gY;4fA_Qq?%#TyN_7D9HcE+)mHfcq6yQ2=Akr#8(%QpA&UUvglH0_Aa@}%Xna9RZX zJi^=CI4aX`K8qV%*U!-xvhB0+_%Uu~&*T#^Q+{zbSl7uIyf+UGmG-^AgjEE}4?Q*( zQfVYBqRxeUu(dVYV>lJN9*FzIa4NhV&m43NH=%ci@A)gGSN%q#bsjc{ii?#``eQ?A z!C|fa)qXa=UHf;AXo@-RKf-{gk8A|@=Vz(+Fi|_7jqoqho7%mYmq8TW?kPa{rl%3YKCeDirgtLS3+e*qZd4w5PW5V2TI%q z1#VH^D@mQC-h0YoTp$VVSR>DdoeXaxJI)tx#hmczu|iu}$Q^vQS`+nbKJmJs53B~q zrPFJGXqA3WO0f-;wiS%@yp5)NaaAMKp0JP_JPZ;hifxMj$hY{l*Q)wW9QfAY z^I*XG{Bs)ot+%j6>#l5&3`;U+ld3lVB5-lHIb>g>e5*R@V^nbmuTKF-b1hC5Cq|$J ze^jk6`rNidi?fmfIfz8-k*ZN4ZVo=C0@@8BnS$wip5KQt#K+G}OWoKSTI~N|<#s!f zc1GdBXdj@|5uOdtv1x2nPO9-pXyUOmByO)*ySw1bJo!cOvSkg0Zo^+?2b53VW>)?Y z-FwZEVGmr@^7X8QcHY8DIoJL$j7UdqAmNXLusR9QSD1+%QRw%w zWfw|_lbW@7<3bq_0P=gh1TLNWA(8My51l1Czo+sNdTB7M@%UnLd}K@c1l-zsAB7o7 zQ)oCE+W-JSQh%1GtS7HOUX@Fmt#u>1Z`#+McGjLkzxXZtp^=?!k3Bx5v?bcOJxZ<%S5*Qt>eO{56Mo9I7{c3O*I=#~Mp~n)2rl6@b;# z={!JrbQr$7E5H@OMJ*!2#NW4UGeo#o@{|$|1l}g(-5*rnj5e^|?7Z>IbR3J^R$yrF zie{zy9&t(oGOg^oL294AM)S3M%H0Gx@hLs+Vv50`2s{xg1+)9>B4%@KMi2bbunkw>FD# zu-Z$b4QXAY7d0qfi-@CjSdq}bAhd3nt^Hssk^Z1kLN0kN_juNPZ#gwK3;liMx!q%V z16&~=AT*N*W68}UH;y>VGkG^my&$;duPpD!$^}hm{u`9F8kanONV%+?@BxRuruog0 zcfdKthYQazrnnZp6P%D+LB3FuHUL5ny;0Jodgyi+xdwV|GTMlL*YbD`lm@LmB2H5o zVfE6eOS<}E5uK&vXb;!;gJ3`7whe26;#VgWd~1zky@wsj4c^*cGDj7k8xyWV88)g8 zFF2&5RW9HHp)tr0;I^HDdoGa%oHh<_>{CTax!lC{lExOc`^Xk=EEa|icm--?SHuHm6mO!66Dx2jhBKHM*H!XxG6 zcY-e6N`EdTRgt!3yf8d`D*9~MwQ*_Y3shqv$b?NnI&TJw&;e6jb7l}pDs=)I0m zL@OUhG|Z4+2S2qd^w&GBBFYeuIOw97^ur3iz%Zge?qU|&uc5bC0PvBkRE@{>$Wxdq zJ8k?Bnrd7ZIx6;E?K4Azs_@DYf2!cye&OPiTy)C7-of&g_=>7e6=&8A2_~+g#+xI! z!|qqj%5t-^q=Pu`Oc?eNB=QcV;*WLyiC^I*4qlb4rmB<%h3f*cwfQ7}u~^1ZrV>Lu30An-*_ft;$5Xsl_}W zYZqg1XG7p7(g)Mu(#h?@9l0_{*Cg*^zvfhp)FCG{0kmTWD$8jzvHI}ehNdrzz)-2f zRUS*}b8BaEDZnKl-IIg27dDZwkL%t0*NTj~Tg#ogj~)6GnCDX_()P*a#-Mi8$Z6#c zhF9b3{MHN^lQ{d)yPnfw@B7O*W~`g32d(Q(=5v*x?ZzTrlQaAutPMv%kEQxG-{w}- zEi0(6z3`!l3`X}UBi7V7!=*g*2V_{P`87|&g5T-Z8_6f9Cj#I5E-Bf2cRx*Z;e$Kf zaU78=o>7y{hhq3+dHV$p<8J%_Vb7Sdwx`KJh7`8KMUzLZmx46g(J5ZvRtD0IWvx4~ zxFcCIuCuVZBnjBaiO@gwcWMyb!zNa&Giku;h=13U^tO|=dAZwLA7@I`#AJ6i^nQ9^ zCbmrigY|Q144;p7I#*J#W@D_-BV4j>RE5IzV}>fuZ)bum;`zRlIz~36{YZiZnstnw z)wmIF@BB7)0xJy+TI;E=SKNjvV< zK&Ygyr)gqvLxmM%<=?OPz=)1&U+b^MGOd<=TDA+LK8e}=BDQ><<65g5r9h5&z113l zL6>n_<7QO;O-}#u5i)t+~NNdq}tS^47PIDU2G+(g}ChD!`CedC6zDO#7kJ<0L}pXtgBq^1|AvW`^wvW=i453{^|WO4Jhh{ zNsGJM_o4j>M=y?*49`a69^0Zfcc>Aul(XG!&to};O@a&mMDLD> zp|}?~Vut$WAu9{?=}8h5l;r}rZ8Wv|8yDjQk+rW@+pR)%@jgWf`xT3N{1!Wcun8oe5~hOi?WdDIxkxL zu3s!rStHh4?4$J}wjlMV#Z6gz5KDChzo%BY`?>z064eEVL$}aFXVgpeY4VRdRzQy5 ziP+_Zvnf5sGmj`itVBS&eNcB;>%Jm2Mdu&CFj{Y zO%QYIM0pI=x`vk_zcFT0sd%$6fJ-Ti9=S-#`n)cy( z&DU(x2+Xr)njtZ8x zryG5;@&@uds?b4h6ATWPdD{y{EofDY{G5@*Pe;IZXP(fR7$Sc!;3&4{oy{ko(*Oxh_rY-W2S>C05#rJiJsm)a!sa(4$&=!cTlW0taCLpjz# zA*f6DCxT>$o_*H1v0*Ux_=lV(gQoy3WmYzh}y>qi07DZ&`lgs zXJmkX`tLl}+sOE^eHXB`cf;fk_~~;4$D*TOf9!|9+)5pX&4l);z|N0)<<0(c<5)iL*IxqZ9>n_IP zvQ<8NUdj9d`S$ybbWI1aJ1SJL1n1uj??&?))__y2drH?uTV; zUg*<)lXCN^?`>A7M>DkPX<+CeNU<~08pP7>ds(-Hxhm84bY+E1+;&-y+rt93q3)0R z;oFzY9^Z5;E5aaHY;k{XQ#Cfj z%DwUFUo-V}dCI_?G>8hg>vtZD$-Mx8Zr;@{!G57Qge{O;z!jex-x(nYZR;^|;MmXm z!iHNFS{MFhJSfse8bk*|^{~bO$S)e8yHoP8^_y5io?)MDRGj5-_ftuYigrm`h@T$@ z>i*yEQ$JbDZNSXFn2ABtR%64>DXME61_rk`t{D4yH$Lu+0g$~u8=Thz*hF*7NQw4_ zg*MN<$0`BZj^6%~7-?QYs^lOZ<181k!TTe;ECytjWE-oPQB{DzLggXjozxkD|53lm zzcanP+Y?{3ZG8U>?6i)>2yNd!%TktR+GyK;$auwgdKVF>IcU=>6yCI}mtIl5jqq9S zK1c;EF_qxd(b$MSA9+bSmFogI4E*ZJlz&><|69MfiZK$O@aAKySjZGNmix)$)eC=- ziZ50M689SpSaNCZZtJX!FW*HJU93uIfK1$6qw&UyYJbqT`%P*c_xz|^W1I?K@hOkA zE|-`Z+o=)J-C^~c!&3@Ui;lMgCNB=ikN- zD6Ujy>T;0r`l&W(mTy_$HX|iL2X*z~VaCX8;`dW!G+tER)^(nhbnW#++8`3Vlw5x~ z_DRj;{1z>KefP|)xqWkE$V2kQU|8>$IXGK1x=+6ppZ0SgQ_)!P4Kr{5b%%?};&q4S z?wn8JF5<7k>z7Q=R$FiXLIoYyrJ>hfy?y#e{Ip3uB%S@bZeDs^298*SVIg`^~8IX$ScKXcD;-`oI%586i^Pr?uYY*_M-{xdA11}<-mFjdw z>OO{lZiPoNqag*e4>wMEO@LPXG7wujLv+tLrE)m9Y2};bB?c~-m4Xo^&^Yx&SY(HC ze@G3@@->Y|hs4H?m>Z8KDWfr;lt6^y3;p&x_)dV^UFQ}4Zn8a60V`?8#r!kiYB3NMsbNeaXbU}MN zG7`YD(eqM=x(u$=()_f|XWJHKX~Jfae3N16F?Mr}vzGW!VI*-7=l~5{AW7Jt1m8|3 zn<5*t0ZY#vw9ABU5Cc|4ml8YF;uC`8;U?(`fn&U;?geryQdQl|rcQel6*;0ije@}U zkCQJtp8BNYB_lZjHroeZogTv`YLboodzMi`@k&n+)$rM)3_%Gp!lFPGcnGB*gW6lab;$bo1z?zY4*CHnS6FD{pfZ!GW_G);PN8;#=EPSQS4F7VS%2 zHi9B|{KU?eA<10qE=$ARj}P31r7j7Ycb&w!Pah^CK^oW|S9bvIV+~_vn}W~&!2|(6 z9OJ%ct~dpibI(yQV@#)i_nMQrG@Ap|<}S0d?bP`P8@7#;AM26<+-7kTk~$vIeJ?1UQuYitsiw+u z70qkjY059i#^(Tn{=k}Xf76uhQcxh`pS)b19N$e8`Ca4LUAunCL84s*V(w+WJrPF5 zo8SMWyjCy>7m9DWI6m+g9XElsrTFn-!#;LPUrOq5H_xaR!_OTbmK@MO&yA{oms>Cz zs?HOlVQnbA%A340y-l3b14&pl`j{53Nf32&v{nC;DZo|1jHE{oQlh?SLw=7^}GlCb!p zWVR*m$tk-hMker;GXpAYzMB3b)N%MU@w!%wjQv1DY3OP>8ETYPKq6?F>$4b={gnLe zMPz=9MysU?M?Ypv;s_o?ulMI7&a%Bqsm2dwR-#8EY7a{8nIzc*d|~}9V$~%FM#Y;F zV=4R^lU#Pmqcw(|xT~o+(Z$pywvOxhzq&D294ZnBWC}1y+on4AcRmabn*&WXE7m zdnYR}5vSdk{WKBHrP}cB`r*#c91T)^BB|*Y~ZNf;G;fPZ5kg@fRcE9q{Tm0 zGZkpTs(IPBIAvH!o%z(Y7oRFXKstp{qx@mVw zg)#HQ8on6nmqA|lWiJ8^i`a7_l7*Kz7-sKM&vATYyZd;@wjAgQgH2hzmW=(E&p?%C z;IWBX{9Cnc{+1qb;^;!jHbW-Tm}3x4-6P#xO2?ok29%M3mGS_3t97=)S%!ik&q(j> zW57^{&lWWe*2qji<8&^vpGdc^s`RI9O-2Vb&%(dxIfilr;^?4xZoo@ixLi`6_)wmM z=)iOlk9G&qLGfk*K$B!ff2Tm&3l87>M2(~R4cG<`6*j45G(9WZ_W*|)LRqjZjYvQk1Bu|>1pe{c~< z;~>;vCcF5hy7jKU{Jp@D8(tZpVbJ&sy2dU2782#%Zk@P;zad5M;@}6LDX!8&rlZ~q;+e=t>ly;Mn3MVNI zPM_GC(iz=O*;_jH*w#wa1M`ll2-o-R2^hu{HH|pvx@zmShHWi9J{OWX{ zvnlX2cT8)fQ9O`GlN*m#2e{ z0vvTr)3QPAu_yb*O+Uwc2!3Z+08qVu8Yf2jmuyLccDMPxF#8}DbzZaCf&O?Ks=D>< z`b$USpA)qKR+2$ut*or<)3+owMDYnjeM!ynkY<1P^PIXY?{SlD@EcbEjwS2FGkzNV zg+cfODvlv_)=CT>nA3+Y6Or*}onm~#Yial0DouR`<5@ov&;8fu`fm>O4FvFXaWP6c zXE5fNoUT@4p6KL7x#4i!c7MX#C8TX8pW%_+L^cz{kZ^+7%{fl{&@%yUC8i_NpSLO7 zI%arOmz^2MO}7Tqwy&jYqo=JFx~WdP#uF7Lt!|>@-4YY^K9!WNcVH`tnCRed*-Rh$ z4@4#x`P8SpqY~dF9NQYTlB76TA`dn+fVLYwdtzJdek$3gOf(fpMFJjyUZO)|wxts+ z%{18oY9G~`oygmFb;(r!wRYL8yN|xHvQ|Xtl%IjmqwqWN#1N`t{8KktXe~%uO@=Ck zEgLK_#$2};XpOcJhW!qMt1sS%M_Gy?7Oa$1YWvb(an;4`rv75-6GGZb9vs6#-hS*i z{?)WYS{_{5%}PZ09;wq&;#s{#KHPjdr%Wrxg`2%PnTGnpF?^9MFW)5-O}yba_U$MG ze*=b+CQ?cOMh1FvB(I50X_>Fk=p17x%acj8iG>2k9fi`pZ>LW>DN~i?iPn-KY0(6M z2z1li=&Ed+4M8&h99m=1&Y0k>i*&R})Fm&EfAAdpIV5a>hgadQUdzA*=21T7)r*1o zQ@`wfqOItny>l8O^R>1LxKNN_`m|Y`NZ6Sk6N*^wvO3hLtIXlJnQ{hdc!Zp=uwGZI zPj`lA=p!}QV~F84rGZ!aS>x!h#ldUj=)B%%!khgYDIHV0M-YS*$3Z#r$ONrqqYhmS zmm{UDM-otp!*ZC^S6le9QZES751V}Myy6vA#41dq4mAs^@f8?{9 z$O^M|aOl`H^S)L%aOJJE2&W%`g*4tWj^K89Qj9HPW2cK$MK+DmL{S{ zcuf>@N`%kEu_p-_4K`o1)S1Yd=#pB*rokW!??*=vs)zh%S-)d(cf9UFuW*NDJ z%Y6EOY|LugG+r9znWPa`pKP8n9$n8mrIwJ8>$vMg&p^@+baqG(-x$jvcqnUUyRjpV zY|@Pr&EhSLj?!G+d0g##@-YsPDOT}Svb-xBettlK^`lRy=#zcZK7CDnmzUQ8$v5)R zZb&vj^4lf`$=hwZW}Qz~8REboE5XWZ!l#vXG_h4NN;P}JVZaQ8*=!%0b?J6(H+7CX zr;hoWUu#EJjIjiY1+8V}!?!NhDg{Y<*CeG3PiG?^K(Xv<2#-qla;`wq)ogq#;G=y8JzAxAK}M+`l`WOC^scm7H^wcr>2RH0-ReI`H9FB>Ri50chO&uVbfgUeb#j zSAJ}Dmg7ydfURwA7J~p50y?@DEinQml@y49bRjJ3elp!Bf1(By{b`jE-|%))6iMt3 zTE>g&*ZL>njQ;uXV#-c*`P+=6<)$crXrG%S*6NE1?z3bWN?VU$V3#L;dsHY=Qoql9 z9c`gpUj+--0zB~TIy$l9h#kJf2tpe%>h+m;%IsVC#Kj)7%{lrYy)o3W*wq!oGCLnb zx7~Yh@hPES!E!L9c@)=+B3@oHn#y+VFtF(__t|V4h06rmbV4TN#E}|jS)P6HpE*Z| zb=!9b4fl>eNUf4Eyu>5P<|YYJdeUHzCK`B7p2gGS5qK;|L-S~xKFg6nCC}jYbqnZ% zIkD5{EM9w;dG=6VS4Z%b7{!9eUGR|`+e~EYN-dGIQdR_MZMGx4uNK41DfWZ@lnlL5 zoq~m-e!V55!6CL2Mo>pfPmCWXLD(O7X_u$O1eY=3M@mY$A9-!?!wHThr!c|ujBgNm zb3vV!eIV2Jl94(DbsxLJl$MP)DZkrGebKYZdt}r*TklEG`ns8+tbAcBiVkbivL$Cj zacr`gmhkDFI0cidGjX(Xz1wVEI~BK!lQPFp-_AK8&d-;(b+w0)4H4Q}0$ z4lSh~vUl`57bdYu-+A;g(X;f6ZMyI&N?Ek8nC;RMIs9O~6(o(v)w}*#o8d6Qi7bky zkCVh^oZ*+xK`O4AR@`SWP%nK2~6ch|nr<8#AQW&sDcPG}~tyC}YVk4fkN^ijnnwL#IS@OHGfCCL`FdU*frp$6-3w%fFVxB`>fhN{ZLH36EvUm_#_5jE<`u zd4UjbR$K6R`6urDjoI2o7fmRQ99YEzJn4t|3_C_{Fh|~~v%g>}pE&Q7vqjy|ROg$C zru{$a2Sd_+gIk{)18(wrzrcU!(AO(Ktj)ezP=CB*OTnWah?1MQm0$(el@6Ami(@6twsOCB92IX$9S zegn9Z%6=R}b7jdL-f-n-*T#~NMTbS5zR)MXpJvL}ltQqwZ?t#vwwNyYBSV=)TR+jC zKJBl+FfI8#rb4B?A&YJFE8?Ra_OKzdFWQKc`(aUm6oT%-i?ba&6Fjd8GMgbXb;H^q z6iZY6GLV~&9QsmPU8;1q=aSTYvWHrsL%M|Nl&0N!C_oqt!ZV{5(tZ; z8Ac)`4WOi%xW$FYs0zLduu zq>7?-0CI*`pM}pDG5}Br1T^K-?As`<=L1Y|dK8?PT z-(g`|(D5JJ*J?pFB<3wW6xA!qqk5LTM?U&99JW!S;aR_Oh8}i-=aAJF6LtHgfdxNp zG>U6t3A1)e48A``NLY{kV%a7b_}xoWA{;Y@d|60|O%)uXRG7BWFGH!{!t@bthpEu= zoTvw+L1kJYFQ$%NV z1J;gtOpNpr{w^iQfBLY&opq`!4%($f2Q!DN$ofx|J&6M%2BXo7uA8pHC z+lQ|J6GsnIu`l7nn|@jx?FK%qsn56}r>IG?QAReK^5&vx%8csh27T#Mc*0HmWw-WQ zE>Z+{XH`}r89jri4(bM8_!FIeCs7-evL7X#z5g5?m+oIG%QmM$=+<#qt8XyptRE>I z&;1|&i~_Z2;k=~f=NW*8XP0wG>c9NiAkIXkeaK$*%Sd0t`MQ08zz|@8jMP+pSASzH zlSqupVq95IF3sn0qxCG z6V1NDnuG2?ni=4Dywcnr-(H3i|*&k#Tx z#T<+PdB%@o?a4>Ky0)#=zLj8Pg;p>m#z2D)#-0#F5>LLyYE8~`5*7P1S)-bI`sGxP zq~!NWEILk}N@pfTA5-zeuHUhc7aqq&EPbJUlOSnSzqA$s4J~bAe{suXrq4SsQctqtR|pjPc`R6!J0__C?+GF-+tkB{$uwEgh7mf1=Uo?TF&Ux*M{*Hy%=K zGhs5n9L-iU83 ze5Q5TzGUa}U+`s~wud=WuCb#n|Ia{%yeIE?X;+(FA!>M0*m-I2Yi??nCg1Txj|muJ z0l8vTX2*YU8bRYETdA$f$*E62UNHk_HSj7e_U5&|^qBS!1egYxucARy8yE13tf zMLLx9OQ)v7xC&uP>R0d5!Eu)#4j9yL;z2How#!X@$FnBSAX&2I zS;~479u@Py9;Z2u8Evx&B2Xey)*L6diAZ+eWU)@gU6xi0(rGvGbz`3TBr^392+3H6 z&xGOt^x@Vog2$h&Bq+ECZ;nR)f}!QYP6zs$*?s zRm(;K2NS3FiKy(Wk!(|$G%Ia|t$7#Q_g+#Q2b zXHt)?pu~5l4+S%_dmWoIFWH?>VCl=ArAL`er7dxMTR~!fqXQ!246n_!C2n!;Lt8N- zA918FI@`xQ64GXyHmj+M43GX_zBM2uH~Bc~L7VnORs2blNji;c$xdT!q@ioC35NIO zn~6J5TFL{^{*0lliEDh}3JF};b{jfEb5no(;!|&hCN3DZ4X^s2+s3)Js|O+VyA`aX zx5NX6G2kYjE#mB+PHwH5qQzr>*}yEWh>s*~^TZO}n45A#506Q2G-(~oRIq*jWO34v zP%@2*x&JlOD3My8>vGHMmPwWNx1I9;Z`WmRay(<$K|8Ni`^u|B}zk=%sD1j|>6>KL#~o}^TAAz6;S#tV_)IEWAIfTEz)jXH;JGMq^`NB?Q5NpS{) zBl5>!O|M3-jlO$zz4vwcZ<{>o=D_YywQ;Pdj7_}R^=Q4xg&|A5TkLB}L=0V436Eh@_>0lcr?(?29JtX2CNt10J%Czr6Y_ehx4v z4vqaR(n#^00n9zTs`(1W$ddY_Ejr}_X6AmO3NMA(DQT?`eNr)xUg>~h%0?IXd4a&eDlpdnj;@XDC9HB5LdK%pPXrhI$OGbE< zu|M%DuKsr03?5kwPRmxX*%6diosG`um;BtOoc)VP#qpUsl%1JT6epDP zg=617vW1&$E&7MI%S?Fuu*geFbN5_&3L-PMPnc1$KGZsFyPHQ{s%^F}_o8nCe0V~O z$9ny+u(%hMQbp~0ptWgu`o4nA1g>A=l>BWfk7 z(Nh2eq0EdN>-l$473* zPiC428%4XW(La8PZxn>3tMcXwOpV-|JvI^aHjIn4W91kvO~PpvMXMiMr&~_s!b!`F z{~%drH)7e$PbCJOw9{-7g16C6#l*B;Vuz7_+pN>-0T^xQ*g^k~&KWm|@b<@HPOXw0 z{{)cF``Isd1M8FiGucOS5*Zwf#t}(&_I)Wn z4u`Pl@)9L~lWgp(4lW5+mdB3EH|665fKjLCpCV!LBsWt_tm8muo4y&lY%_Xl-@C_P zMR(9fIC~phdD$^JfcVS-;;{E?vNjqKG&%-qE2vY7XIhz5S*=SA$z7c1)z5yK5(oM$ zOViWOZfhs>au6KhqBEOF6pleJ0wybrNPXpzmVKKlb!^t>KI}YJ@A`-PS-(2I!m$5= zDL?*c?4T9Tv})7nRlj8gcx%j*p(;qFY4>A%P;{PpcgHmz(B|iM%+gR| z6q8Q(N#*n@hpslC_nH-(?1QarZTTe!!`Lx6I+31_BD9YG zXVz=SvcWEEx+a*p22Lx+#*&*gy4fer%LcK1*;h|!)x`urjJ4CWG>v}wJHIWeya^}< zhUG*S-Bpvf1G0DZf7;iHT0Z@WY`j#b&(AiERqCITb5h&e(xYxQiwPV6+TV)U@)!v_ zaVDdQX!*(~@rCjqcMK->LuwUA_$Vr`V`6;QfB5?~3VcrZO&9`6S^mAQI%}e(W0i&; zu};WgXNK=<&gIGs8?L=8wix7y5xmCIDPQC9`7VRm^ZmtS!*s=5bM6q-=MX3WBKg7L4t=I3zWPgd>VWKV|C$2oRCBpz%6cMt5$j)gb+ z*3;V@`2%LgvE=0V$m|%%PtADJ|0c|7w4L%-RmZ_eu0C;&lM$+{pbS5UiPb1;{Lrta zT~2rb-2gOpCW(kUZ6yE#tzVGvh>{ZBvdPdHJd+P!aE4A8p>OnRt9H$^U30YFO?dc^ zaR~N)qV(tpir^KIai?wmcOwlGvmsGl+ZnrJ(|*aLF#U-K*>RR^D0aAQtISb7zA?=H zLlmF5FFv(VIv`K{o?Cq^iP6m0k)HUkPW&S4+|8O0&}er}DwL^-piXJF&ZgN?rY_gW z?5%PRRl0oR++Au~N2~pvuMa_WRi&pK0nbz(sn5-D+eEhR5}P)5YU4E8N?eX0yQ?LW zSwCXvzg1&$^r@y|B(c6Tsk~k?(u{(Oq^q@!kDJiB_b58azmk0_(dXZBPTPCNBO#;jH#dR+GF;PN%NG`q%LASmysT z>RWB_AHh}^CH;<>{TVyPe#-K%;Fgw7519f*el}5i9K=$z@x3kn6@S%kmzXTpH?&|!EUWJK91VeV*x)#!qkp&N(KdI(iMEE< zVP7=sCa@E5Sb;eVP5@tScFSZ4XcTLfm0w0u9@=n>oRlf;b&)LV>M4!f(9}98lQOB9 zNa#S0G>}!tp4L@%WJ+-MVn(&#N>)4uL2K35V-VFwe>RB6V-Ux$_mDWU$3d)zd>6lg z)Sjh6%$TOG4JMcQ$_HVDtv36H`4!aY&3~g&Yiz7P9+6Ln%n-x&ZP>7F`ms;0Nl@|v zqiz1dZ*Z!)FE=Z1Fw{PMu{^%T0_@eVt(Z94*GD5n+mCJVnDjbH7(IK`h-*Y}T)Q}! z*(O!f49vX`*6kv8}PuQr{PjjM?))!kww@PxbmN@a>3{k6@iHOHs_uM9t^2YN^0KDb#)j!&a&lCFUALPs z(gm)YzVVhoSb;*INxQ9zk8s`b;JLlLT$ERddvD3A@ob)7Dnnoc#V01*@w3W{^;B#pDXh z)mNuBn6W)n-%L;}Gqv^U(f>Y(j+QLwNrnS@`45Axu!-zY!hCFQPxk<@;-XSanO{Vagx=sYlTF6n4p}{pP6gK zba-g+3GpV=bUw23YkAV8F%bF{cy@87EH5=kVEBSyNxoSP$roifvz5l|ADeq{6~XDl zVC%=?FEwge@h6}07fB;@RX+eoNjiK()`a%-Bj?MX)EP9nv0G`!L0n$Cwxg{uilKxu zT@s7aKnzVL+aNC=j$FPCOCyS)(SZWps+8k6F$3)@0c~K#DXi*!w=88$kt%?Ox~-6j zD$V-NF|9Z{i5hs5YfyHKy*4_lKr%?n6OQr~DF_qpv{~zNkb7jC z_q1AxDdlCLq3``Gc$rY%W78jx-f*Q;BDkD?;$K7e%#Ierm$GhTlAKIvB|rFtAu-L* z<3KNYX%cM!W69*0JiHQ|WfG4GRM*S{jd4;nX(6XC3h)$+emx~Vo5+n3H(8t9@M{$f z^(Xz-p*B$+Gw^huyLNkjrmxz7@c^Nf93W>J#fjHsD4weLV}sweMeJl;fI#!aYF}2X z<3IPqph(v~Ksw5$Js!~81EldN3{m4aJnNnEoOH)e;*_X6;ovJ++!Pz!iPIUYC9}B3 zuIbaPaOss@2ml29Bwl`!=B9n>vQT+x#D|XLY~zD*E@jAl>AMG8#Q+zYeoDfwLNl2Y zhVwfMs`h1=x?6UecMWLh7sc*s4~R$JToAv*HS*&CQA`d0kv3F#9igx9b(v^dfdRh~ zLaQM?<$(JdZ?_6E;A87ja4fD_9L$;>;W2vxR{?QgA6|~)S5ePbJG9QwCPL^AWC;P` zE>VG!-QW$AcUb3ER{G_m|M1VGkbwpUAS)EMz-I6CY_{wHsh#lXKRqjko0orbTg&5i zrrOlS*)I*F#5@>vTscHS)34wf0ech}F*~-C-F(!2FNnHHZtpF2RF?jiv%pnw)XC_i z@S_6lHSq}~laqDbD~nD!B}OW%uKU{(*2xpALG&AM-OsE4)WJyyZ{GMfW;{FV`wZ}% zy5&wb$~k5jZ~o8-lip;A*fhJ{{<+0hU-GaN#(KuzWLg+Sqe&mxrXX8BN>)?{(4$)A zVx#Ksa_zB{GHs((+`%4+;yszsUmJKc4`%ulqv~@%>Hx?-OG&+N+K4Qjb5@F_tqzNA zyM78*c&-czEEfd zVx(!4x-2%jgU0k%5Tt3Xb;$L{Ed}VR$n45x`6fH`)YE=V~LR>Y1_@wg?-qf=w6@WGN@LK7^B8G z+s20Bn!1J#by}ZmV(&b(84OMctSKQ-eqL=s&w)7A!Lv-VQ15Y>E{y;x*?1ZdOL*ur zh|0;r5jlAy#6!`8)9)%$TXt-!$*EG^nq{)fz;!6X zWaQ7j&uP#eQem_b6O)2vj{D0{Xp3jcPPcun+f7sZprFC+k-9e)CLt<4qdh+5-M~?; z{q|Et*tJ-_s|0D`q+hyBlazY=VPExNlB*O0Z<5pvkI3Ahn>u*fAC>`2sS_89xaPzH zkyiXt7sJizAnss70K?XPw#n*}kypr>BeE?7jm~$^y3(UfusC+I4!^{Z!V>Q}2~noM zbTd5v&u0C%j*eEz`e5yXP6#p94m}5#~)-Da3A#HQx*~1 zl(U>XR&rFvUi$lC@falWGYJ^&CY6I$yMpRp$5w30nP}0YcWCk~Iy&WZpogak5Vqw1 zpnuU-o=$+maI}!0Q<)x|{xf>n@7n~sZP1!j^hi6~403xYj&z6%v;Nycy? zRwK-wRr97e^)~ukLQ}uIve<+UC+V#8;OUZ9TRly(le}gnDUB5v^oM&B-Q0ZG6H5ar zpaF^_S{ZUZ`sBScgV(-}Iv7ZBlr>HJjK7E5TeRJh$b6i`hM?+8lSBXWGJ=c4{4dQ9 z6kv8d>l7wahEg9r+CRr=5}MvYr@YW>U%sd>lpX&uKnD07isdjr_4DP?zg9#P`dXYi zE%Uzq<_KRFrPDbybA%9WqbtzZ8XMc;$6o%@Tis?*j(X{oJ@I9{v|@v2`U+L~UYSsx zYVF!x7fndYu)9r?^{A{Z?x1)dm7;!^{$QOWZegfwmoH92m2q~w8$l! zws`fgQIZ0oSEvjV?;5L!=2{*e3e|=_U72@h|56e6l&71PcMOk>t8Z9Q8xy*f4?#R_ zi^qiGYn@WY66?64WCKOOqc`ClMR3HS3kypVF(c5@iYb+jnprZ-A!KJM^)XbQ{}bRj;u>% z)j1ilW4~j^&dA8enKhZ=XDhw{LgEPFPli!kUV|b&I z^y)OTO3Twx;gkgArAOq3Un{F~Q=@#<7yKv5pht-cKjuij+5?x?M91E+aS%@o3~z)a zN0_!1PY(_c@F4eAla83S>wh^AWV$vx;90Wb6me#ZD|wMpPPm%cm7{{4HLZe~6GQEX zf2zSmRg+;TqDV({9qx1r_O(;a7oKW>-_^Ld@WnadDYTC*!l|hCiZxeL4R-JUd!%j> zeRxnWPiK)u-5H#=co1Qz02|Tn>qGi|o@3N!6Qi{BKe~@CjtzbS?;mld#nMMcLp>0Eyj&Bmn5r+`l$Ura z22P!U#|j>`w$mh;9v{nhJQ`Z~kBmx*N)qT64~gX!7kh%Xf@Mc%-|3AS36l=~9gUPD zB{ZFqSY=apkHiNDM*~DGKFT-AIZLDXln^aTCto_Vt91aD8L}J%6w2eQlavPn2;|#B|Is5Udqf|4nC6j|b zk`KB})B%8pHpGk3YJzYEMlv=a!{r8A>&AMs9`;(30}MtNrt}Q5A4o(eC+F0-u4giJ zHppIG9>B9twuu;=RP;Tj(AD8Hw_ zz&*Wf-zl&c0_ebALTG<#vn*86qrl|cDeo=QEjx}B7+JQU;6WEQttnCU^tfa) zJuPCm9>FY^pumyb#L;VO;)gU~y^$3UcjRE-o$^%dprJ(%@#HHHXH~Qttg?Sx@A)dY z?09hIq5$p6>r`^lT32^iLpgh3@=pGuw;|rxV4#J_z9TNMSz~kXn4%U>I}y}oO$wkc zUBR$?O>alzk*!$=a1)drtv!|*j!QQ=!QFueT+V$g%Uhl>df@hnM+WsNDWF&H#6!`} zW0acjxr|KG>zv@6v$}ALmgI{Ce>z zL2ya}^%Uj3vzInC;J1ot0alDuB9#{V2y$l-MLtmMlhQzWp5W?Sq{ zR+JweNnl<~AfcU{`B)Xn)!Rlm1s$7@9Hj6UZO5zk+u`aOJZM=hb{2YY=B-~)OGVI# zKZh)~nX{?QseCCIoQVg=h=X6(2}2qF^}AL{rA-6#?)*^11SUVFxZ$x%(a5Ht82ZJ$ zc#kfC@&A$E@KvU~)oJvXUpfI#UW4Xid%jnmbm=qno%Ur1`om(b7Sa!tm0L1`%G^-D ziG)n!wpkecnZK;V+h$KarJrR2_Ea=MA4+Q+PdkhFpgM6L;!yUmhZ}8n5oCSF)G04^ zB7xx%QWXgVzSr@FvfVnHv||Pk=dzwwQRLR4*0}5$eO987Xdf(=O&lc~_8>j|+?n+J z?Aa-g3HkGD2QNu@B&ABf(}3HhHZ4<6FLseI)Ihv zx(I>Z-8F8Y@o5duy(X0K7OfEEb_G=X0xDDaq=*-BZWdE4loWa?%@}e$llh!>0`|&;P;0g3UU&++W=>Xm%iR<+C*>-Ywi3`q20@Ufa12e6&Gm_TH1unfL z1SlWQ;Cr%txS=1Lfq8Ekl9_!u7-p{?io@r}XWQu=3GBlas@!k)#}DXZ0%h+#Z6x2Z z+ayI&dc46>6PR(rRa_pX)32?KdP-?*@OdPG-Cw=l9e&#TZ=JV#zGJW1n(pM0>i{GJPbTb?i!(wm2|_8?V&+V1aPWrZBYn(>e(G z!x#-<{9z(^XZUx1d3(m6abW^-a^T)spnK(a0Yp9ggSHrkyJ2b4XdKX{e6LE<%L7z{ zLQy@i9s5Ubfa*iMcAud7^69{1%!%viAo#6OLgX!9&ept_(Dvw|vBAE#*eNAt`QFR> zj^5?Nkk=G96A!{F8``>9=xqCbAh|y}DTZqH@|uqn!^8UEP@4Z47zT=i-A>3`V?h03 zaOlN6sSdQ25QL3{NjmT-;mFINO6&xpJe4PqfoeDUkI44Jo1=VDhZl*S+G-#99QYGw z;*6JVyUqbdhJ3s9Q;(x7_}R30X@3cZx_e3MEsQN_X}6xoXtSjHGm~0v0@(bf6+Utm zN!{%|qC%ez1;I7z199Ui9)=EJ$XfG0toV~0Vi)OdJW=d6lE^KiEk zODec94nB#cTL2W#cKUF&oj-oQT|T@*O>jCDH17B~17lctH~mR7uru%}!?y$`E*;!y zrh&mTaKtZddVs&X3-B+=Q|AQv9WiebGkH4On%gn#7Z0xX$Gx*MXOw)OxPSu8B+_ez z?CTV!^&HyEAGQx9v6E*%fcxEc_Uw<_$;BVhfuwf9jLn0u2TfztnUuEsr+(_nRstdx zrXMH%Jbo2uICcC_RZrV24@jln%Z0l%;?Pi%q25|6u<27}DOe4Rwc$yT%?h-OH=4?- z`0=!dVMW{UPLFty3`HB3P>fxfI;sRqrbxly;a<10GtO#)Uy($gmAvIiyAhiG)aMtj zhIV+0BOQB?Evs`3^(|%ngZmA0)pkq$Ri3(-s-@8?A?|Pz*S}%@ai6FKm&a^UiD_q> zwA@cp7#=oRFBwTKL7Hd6wAl%Un0wolPt_q%65JD2GKOC)!VfXLA_Z6hVDh5ep~Zbk zO#ZM(pFIf~Tz(3c_^BVXzzV2fA}!kJr%vxcOL-Fx|32=0JLuRvE>|!1T8~MG?}M zdRA8t>_=ZS$vA#+M`;Pzl+TtJ%N~~l4{kcIy2lSTzehn!A7Pet!H_`ygezq5G|HN8 za*DifgRxu`hMh9+7e^Z2H8$%wI`v743>fU4?okeZpLxj|jho$gN(m1?6D0CPi|FM$ zTF>G=Wn~%p@&;#5mb-i>+rm3i#zg*$kKSX8^&GCKV0H3VqdOwL*_CocW{{e<#!ui^ z9-V7;)XN79>*CLm8j5<4frk<$xq<+n=PPwH03>p^{|&E^4csX?I-10k~#q7wL(AmkSj7?@hB!trkPM4Tnoj+bO zZutY&2dAIhU$IW*S`{DGv`l~pn9eB|GX3#V6OyME%0zExc+FxQoT>-SiKi4Q#lPmv z7+@wu663^`vsTLRILcdICbQiP8Kd^oMG7wrP%nmHRkF0bxS@`KvwKc$&t76LGsoL6 ziM{tM>@)ki{gH&m%lZBoOn?}UgJPHwo4-}d-aYMF#pK2%?dW`eC==>l?(i|wM| z{YI-ez&biwu92^Lyv~%B`uR0{;37c!;1V&Y`7=luO~cc+>{m1f@sYz)mc7G5XOoPM zeq7@sS)IOQ?VGhLW-@-xjtqF^lK;q-vLhv&qt3;&;O+Xb_*OJ-l3VxC-bN_qJw_CIWgXKzTf??__Y zvOyyAfI0oG9-3*e%s7)XU9M*RHM6H=cy{o`KgMM}p?bmDJKJ${C){1})QNcYco-In zgHQ?fV5gASwI@Vpn63%8DS*wI__IxE;&KT?XEBODbnGA`Nd0y?W!sP1z^Ba}x|{f1 z0w@T;lMx{He95D>p|xI&$bOTroX0L-A7XzCP2N5egS~>03qvHFiVjcL@Df8F*#llUTvn-RJ9V_nI z>-0`*fb~m73|e(iMl&bdlZSkhtO01fzqMb~MfjPxf;I0~aAKVXtbZ>wdIf)kit zd^wz5!4l1wp13~Y+LNbt+Xs@7r(7hb(>ub3Q>%;97u)&W6%z^CfE_%Qyf?wo?u-g$ zj~YtHn|JO#HSKf*kV(~KjP!Iafblz!BWqdrT}W6sv}uAG?zeDsRL-WXFc-^-{UkLE z09#w-XuoPTwv5PP%YGPd&~GEat^mjQo1ESa91dClCx;9C0Y_(%ch8$qK3+33yLdCF zlrH#FK7IanJ7?kl@fou+7W_{xxU##yc@O=J0o9sVU{8FuW{B(#rfM3J${{y ztTn}@?T(i$;S1auAJeXV+BSTvfyAbK9F!`2EtLRU8u^wi>!*@p@~PJ?d<6%~zH6fg zJP64{80y!>W-R_a0NE)XsLF$a)s4OF(b7Iobl5iS(j;Of@A_)-Zw<_j*95=8eJZQS zz{V)s2DVd(KeSWYDG&(m?u>YsL;?~S3(qKV@Ve^@x;hrQz`9F0FH>35^2sR3Zx#i= z#s`x00zA5^XxStNnckj7d_W~6fb0eKMrAseU6QPq<^G(aC`OeS29*u{}+ST4G& zA38VlVwP240Chl$zvFvPFS8qL?(qkQ^*FFQZN#yytH}z##Dk94M_4d3GyRp#vsbRM zruZ47;^HIRd_E2*cJvAGhYN(hte;OlptG@X#;NQ1+wI7j*vaLaZM**CcK_lJ+r{xm z%=sRvKcEY@WOU6RzW!w^U==~ADk;h@^>i~~;YnaX>^la&iOpiOdYh=!{~3gSGKwBt z_a>ATlbwDlOAJGZ(j)_9$v4pOx6K$G7<>EAYy{o()8N~OmWsHQU@}cdN>BW;tItiu z_NDb^)bKJEoGs7nJNEg*@e+OVmOf7HA`P5+88&lh%U7$(f_9eYgi>L551g zR889~NH+`ldRvf^B$&)(D=j?BYqS*b zR1C^TW>F6$s0U9iO;|`e?W*Agsna;_GdyfDf|dJnJ6yt>Sv zO7K!PR|sdxK;5-h+w4E(cT5hhjh+gs?cj+jjdkGJ!Opr^+Dc$6&*T+QbE#{CGl(u& z&^Ha1_)ILDxYUgk8@`0?JgH*JBS#(ev#ncj^oys*1lOapQjd!G-TR3z9YRI<-cy{;v;=h5qa<%J!ErLb*^uh0)bypL83e zwa2?nNPxVhhp(2*L0DK05o2vq4IUPRn;hIwY{N&vV)${Vp?MBg1_HXlN1NP})_sB)+Qb<-JY# zUS7j$2{BAmSM}ZNuDlPvRKv@?&Z%!2_0uY9M*z?!`7C@$>4!FtH2W!~5s)kri{5ik z%ngR{-|?AAsf+y50C~fk+Rx<4d=|=wHk_rK9pe^H`I_!`nHnK%$)7j8dZd>^0cfb_ zV*(J6__f=XAaqYTX(SYG+1UWP)25wTE4lqP2zKh=OV*V~PCE@CY8l+==Nim`;n7BA zW-rzaB%zd@ZE-Kv8TV_Q-ZIOe-XQCg>2y1H;4-2QB&joC&bB;F^oCUxOH1lZ(+EIn z7?O^FvO#f^lhQNWfX!@76|5{TnEaS!b;=Z&FY2SJ>u&~pjK>yrG6||H@CFAg)d{Tn zY~l+&ebWVgN#kT1ul&JWTqL#G_k7b!P&t*J>+AS=Y%<4?Yb5ey7T$ z;SVG&65HbkE{we31pOIt%FVIeuA(sb>LX2&Yr6VnL*M90;~aeLH}H&`1~&(iWnZ{B zba26w2H+lly9_0aj2WSYX8u*;v~`w}V$kdGWGVq&hd93Vu})$$%$gxu!IAa5ZJpOo zvZBFOHtnb zzB2T{rWUT94a<%RJ z9qT(Z9SACe=!8J*6)+?qZ$vM#KyduPnpP7RB}&2{YFJjsW`H^gA*UoAuOh;YM`O zFylJjk-Tm>9p=jI;ll;V|EKN1EX{Q>p8%NI4n83s^qHIEY(D5;yrLL;Lax4S2}Ca# z2mVQBY>fd!TYb1?zqb45*@&GqqS||EH`@S5woUvky!Q0pBo!qoUwziu+mb&nq>VZj zwJ-iw-=!i4nhv&uy!v-=Rb|>Nj76cLpm*pSV5$#eeJRBQ7P?U zFl7U3OA%LhbfixW3=DoLt{Cm(!IFGaF1G*l?_G)2U`@#Q?IUO0(VVJJ0A!- zWqHJGmc@W`kUzR0Z$iW^iDgE0cKv)izvXkyZ4WdhhvYTWbaZeHUK)eZSqFE}YinlZ z2lZFivBSZaba1`N^mIQWW0U%H+UnY!=mj;@k&+5MKRja=`vsc32ao&69=^j2ZpF}V#NU1|mwN1{gPPGy)MW+@ zfVY}u1B$7eFjUE8^%+Y;9DC&l6pKSPG~}yi#B~Wz0HY!-9peZ~w(_alW^!%vVgD~Z zEJQwPfW@XiqZ=9G?ERS2^EVRUQOMUHgXgX+t{SKr-;Ay3S2#j6{o-BC@k00wA z%2Sp)!n0FHc=^cEK^c1A4_myn!$8z<`S=%11j@)7<|~xy4QV{(#Oroej7&nX;5$zi zZT6IK5|W8QvCz*F5w;qEPHBv4mbAOe+}<@a2`6B*<$<*-1AxZ?IaMTJS#&>j4Trnw zU4TEZ=spYd;E-rcz$Hv{lUoUavWcrjbrd<;p=_yCQWK3+NVIKD1AR-J?!6eG{P3i} zei`cv%7q=}W;K9HyfI#b4_wVxSP=Z0nJ_7CZG=s}r1;1|LIT;lhUFyz4KcZTua}H^ zgN5|@ABZC=o4}Fqky#Rcv2XIf=GMuhvX>!xmJ*G;kIMBj*Y6l%p!s;zC(vfVbfCCq$eaL-)C@2+~^=b;r((^i9fYE9WBJ+eT2SQI{X;s2B=gZ;s%=H zYi))T!h)1|CJ4q3mC%F0(5b{Zefm4r#V z9?Se-v`HfmpiPJ5MH!g%b1Q}YGsO0<2X?*MX_)eCf}bk1ux}>_qb{L|Og)=fpqt&` zP19iTH+T-+$9op#1})6w*O=!{LP( z-fp;mjz_jmdA<^=(9P=7Y||J= zcMnj_zVOe?q@>{kzf2MbT0iPn0+fd^6QJ!OMg%mW$*J$qjg+j|(M1gKy8&Wh+3X#4<%iF>6PfE~$Vg{CGSaXggqFP3Zzc7)UxFMvxIr^&l~6No`8ku$yW zo^;eplyL(e6oKhZ#etKjpjE~q937}D-%he^W(;5l(~Ay-LA4k6(=qwQRl1NR@T2|H zOAf9Srn5aH_E?UI%SG`U@R)I(xF=y9ZdsToc^$le%gb##pfRq}2BPf8U+M@nm;tSR z2uPF*=58|&0t>m$y2MGVczz6OT^2d5+C!Wt*O-8HjpnZDB&rFM-?Cpngg3qss+OJF z4o8V%R78r4qvM@qWTu=+2D}uQU2FlhXuR|FfWT&I>E1X-j@~edb|8U=Y~Tv33(B-( zzlo^3`t=VFsyq1fp?W-WmGbW0*WCT_8h3HJ&RzZ6vvz0HK_OwjfUJl2E5S~(Pfs@WE8_1tlXI40~q@>eLN~#0?1P7d? z^%I?C@c2`{K|WJy`OpOIo?zmsBEf*u-YpnrPX|7LNlb8)7Mr`$qbzk*RAp&N8FM~&)qaYW`{JCd(%w8w-m{bGkwRAw9XnXnJf={?%PNfUUv_ivY3Kgw6>-gl)w z@S7V@@eQYoCqA>t=irCLb;d6mA3oqSk8T9Z9rWcnDEHeGiVJm(^9*oJ(qGM7Z1Je7 zEb;tci~;PQz{(V(a|9dI?llNLU0FrW%m~KN37&!(g5ZLEWf$853xb=bYMU7 zry5LMV3gk=)ZeY=sIO$S@A5|e@MspO4lvgr9w<({AB;C{h;Ph!(%$Sx9Bi|@JMf_F zTBk;c0{0Pg(5etrvH1w~MPka>M^?W@esJ%18s%PoG<( zml5D>UX48L7(?1VGcYWqT>R%Otz&~$olL?yZr&seZhav`|Bc?^o1ZK%x7eDG^kbOD zElkr*8A3;nG!p8w@!!CWNzi~%UwkyGdLT5I#=!%5mnqL?Z}k`YN-r)pwwz42z+TR7 z`(Cv*X&_ENGo-re4&LBs{zr&-Zo=Fqc&tsJX>OeDB^*0Pjxjn)zi>dwf*x}g)NKmv zr4!$dQcNBCv%2u;mJ_WTtlkeJQAZLxORvz=B^cVrI`~qawX`K89Yy7+XPfdz^zak5 z%d7>k?acZ{H|-a7;nC(`H1P6+dI%$!MhnV29|_^_ET?;sM4dQ@MnNWf6RN>E@8W_B zyqtD#B&qEhB$jf{OkB>oNNA_t%Vhu%5Z-?^YeFeOT_LlnnkwvTURocG&EXlA1Isf6 zD&+@_zDFOL3GlD?3INI#2M5#mLoO(P&2 z;m!E*VUm=$=*Q#m!O&U%8p6>?KWd-7gg!$(r-Dq!PhIP?AMwA>UU~#LGF6fTA>HYf zr+?`S1Cx!(-e7s;^*=`v*zxg-rHUWA0rvieyZAq0K%Q~i1#iMAUgQIvMmc&qyFk}s zODK(Ku=KOO>$+%YuNC>tSlAoR>8(8muD%1yez5Hc-x5vvAKcc_L@kNPk8-qYx7Wk@ zIc&vG{RUkz2X7ox?C2BwPQJPa`eMjFq8vNzqQSDuBA_k-_-jn2foAAqI;eizYKv{U zP=-IJkZv|QY)^XEA){sX%XF_X2>FT1+mM43Qb?~Sp zNsdkojV8QFP{I|pXh|xaA{t@wP>%emC`fWUFgnRoUK{+@w0uo(!M(oeCO_nqWFzCt{>QQ217#9u=EV$ayCpnW zpx<~dn-`I`Sl?qly{1OyNHAcM1EEsk?G#@%h1aLl)klblXce=mc0le6Y;7)E9zJN@J!7$uXe4(erC1Kok0!F^Ls9dceDwl?EKQBN2mq4@RQ+{$JgwX89-fmt z1ZW(LOlUC}*pvtMF~PxG`_<1Lk+eiAXM%y6U0nrl7S5p~=DZz6`Sw5G z(6MrJ%bP7YeLOjR#>*4man>wUYgN)%(0dNpJ*f$!+!|N23RsS9lf=T6C*5J0&&-xUla_i% zZTISc7u76Qs(~&~^=LhM%10zx;xYJEvxj{z8l_Lgi z2u=&)R6=|xaE*ZVaZp*db8NRKkvk-DqAV5i;n5>3cC10-0A*(N$PCL{9ejIoFC8U; zosh_Q2ATVhuot`&AMsHMW%DUD7IDMN$TGN;>!vqnTMz=ruG?p7u>q@rEuG` z@{tC8k!-DY8{}hO?E^sGVYn_g$W+xgs(6W$-tCtzBMe*_$UiITKkI?gE8hkh9Y#AS zps%d$I|=*rQ~u6}0jFA?x_0F;=G3t{`ye(}s2Vg0w$Eb0LcVLMk3NcZ;LRNHx_|tX zHtyo*O%^9il~3t+=E>XPcKX=JO%AP9GFgJ_b7J`_ntU}`k7yU@UXUkfuH60|ScFMM(3W2if) z5wBq&+*sk0A7KeZWIm%y2UE%%3Ton8OOy|F<<(IVQuo_Utbt5vhY!xs>>SJf1}E0% zsJuCHnt|nU&w;hB646x)*tpXm_fNaN;?PgNRAW-BRr14)BJ@55|g+w%HO zGz;JIoE}ne!QWmKAN?lDsh2V#vyE*RKYXkY-JZ0&a3DS4eXx)6_=2k|@lh`=t79yZ zJb*#M-b6*6wy^wSGXM)7c+a5WNJN5qyn*VLQyXreJav7`nb*2q;)EHQKcd_C8a`c2QE5W9)hf&$s(k^~hth-KAOf5iSJ>rWiBlqH}w+c~N z9BEm2B8W>uoxT^(X6>_W@a+F|)fJvf?)G~nO|GDNi%?hXxW(duTP#?)2hIx?c>wj1 z+13Y+KAhYWdfbA+74>s&czt9&EJq|$f5C%+o!po%`KrXn@AL#^yfV;Q)LBa%VA!PU z_A$@F5U8lWvI{QOvDJNkkd8JvIZO3|+Vq_`i@~COy5}!xv@JeftdOnW z^+$VU@#(^px+6g!N7Ev{A|>!I^V?5thtSZ5QnfA{7fjp~r#^HV0j)7^lJG>;`*KLL zT^VGblTBbcTw?ix(~+Y3z*>(I%bcTA;OlJ@EyRlte-9_}+mnY3dmnzuXyMilp2G0d z$oaYaB|}-_t`3%$B7`Zcz(-z3GWci5vNc`6MG6G!B))FIZgtW|oxiV@fKwo}XRzUy z7Bc1MNBl(>HO%HT3?|%7-pd6^RNN!VS{F&kBu4#&MAkyRvn?Oa_GktztsR-;9e*S= zqfjZziY%F+DT5!4)L_ulQMeFcD-sMO+nnP2XEK22Tot4&x?j3PZmT?}X(q_=OF^ow zE19b61U`;Wq_m4~jVi&v@@d+0i8(WwHps)Ty^)4^Lf3$xHFIShrt&GL=xmh)Jl(O0X%EhoXbT|G?e9rhAa`vo7+DI~+GXZF& z*JOFvRzdqI3mL~)h)*I_%Qj~B(&Pgho*b)B2QFRlKaL8g_>+Ad$6h}2l)k<6)Z?v> z^{??PtVR3@)Ipi=(FsJWY#&S3FxVa2zl7^rU1iw2l?6y#6!DC<>mb2(ESOVAiix0z z07nm7SHmVs?!lE!EKNw3M~ZyAnKjQ-VK3jO)3|Wa8K9r=Md060vn=@wW9uE^;Q~`e z`VP-QLB4vKocaTWa;GLRbk;OUclimT)&KF|XIKGAWO7O8X-%JeRqv^yO43+>uxEg` zuEgoc{Vf`htoqH3_wXT!9Y|s>)Sq#SL^r9*URrtZQRjSI$(|nh>SHjq2fFvJx@TK$ zHBfiYU@GXO(MI~8;A*BN5v<+iqg74;(6wn2$s8Pv8~u=c`VX>c2kO>+Uu%HWfkB7A zajqP{x*=8S1P`q&CRs`}az~m<0ReZBS66ZQ1UC+3ZA>MEIziZhN?vj_F~pf;3I_@g ztj;8oB@T_QYPoMtBQIj=c;s|3L}P0{kM}$P&aNCBceL{h)K|B)F?8+E?e z`HTM7NR%o`s++T>5{_{3b`t9AbW)f)wU;;a^&`MEv0X*z|G^oa>Z1ysLaOYf9rybN zA{(S^mi<_Z-Kw)&uxYoG7M%%+!R)JM8UQAOoPjizD>2|Gv~tQhZ3B&L z(>se#saaSaSe>`eiohFdMuvK*S}$;8-(~cbc#>aMg2n(d^oC%arNOb*tY4MA)!kwKZ&MQ@Ia6Fs1Hf57w)~{ z=qaT)uiCpEF_GdO;GLEMtncKlS}^S`rqsL5K|LH?(8D1g^3|s3bU?mGNVqdNhF@)i z4eipCdLpo2n0mLs%ZQpfx>J}L6Y7FvkSjS|Auh=Vvtp03Q?pfe|1XlI?WqQ)`?WbE zMBC^^s7ZZ*%uTqo#S=RAgDCMwCdv^$>!46lHwl>pRNjLFmV&@(B5`2o>wnKafYv8- zj8YbUlZ;bFyWL0Ad|z63&@)x`^inlG-17L*o4;xn<139$dCSGg)sM`?IC}NC9CDD+ znFjnC>?9VWUzqaJ8~J*`@buF`*mj*oUq79xfQQ=i0a3lJS8yM{RX4`;KYK4+$UhWu zltG)i}z!D6JY;K)kZ&2%9dfDd?OI|&r2rhX0>7;a% zDBxp~fu?mQP~_3GkR&co`OFj6n;prwgoeJ(-WZ^riULnJ_QBkNS28_q?urC3l zXR<+cmB*e3k$-rp2Z>VH)MucGsR`~2Joh=XmT4=IVYBO7vWy4quq|O)SevZ8N66%W zXt+6`LzFW-1R8ZQqRbvxDyr*XL4%z#5H^B_248q#DBlfRh4?SW`GRM%gqzsw3kR49 zO5KLQ`@iC&3dHES@r$kXKSi}kUt(qVn;~M2S7Km>x0Vo|-hd3fKQ(viEf)42KOl1C zez6ZXyvo{luKDuFhhOu{NW5e0>@7OPD7{U7Ey3V76ZTy3N6a`|Bf^uytCOZVEmluw zWX+6eq?*2BjIW1BeWb0B$wR!^!~;TT=x1YGmnYVOACu4Ozd-V^57~#`p0aRB*qhls z{cJhHzAdg9f55uh?H)YWgH0h&q()VD(L8DU?!(b zPW}o;J*TcDk!DS!#OSHqB~Kgrz|T&XtkXpkT6$VXh%jH|R(n9g&=MvEFt0wtkj5m?Q=gRr2U;?-z9t(7))Z-4v)d!&7O>c7Bi4 zXt!M4OJ^D7(g{?Jbmx^T@h#_9F){*}{ii80k`kExl!B3g_P{>Tp|e6=~C)Nj_}8W#L^<^y-@Z%2Md;GQJL zv%j~zmyA_S{yuO@dCe)M{!{jdIfI820RIH1DupM=D(l|~zU6%mH`bPbyTh}zG%}YUTkt}VIRKzz^|mXsS_XR z)Jgk}Br+11d?ehzEe&+Ob%E(&Tr&>&*dlI9u+Ic44-ClytU>Cq9-a zR9N+qT^*cV4--_$i=f_w0*Lph5&)D*K+d*KdPq;11R25~d85G{Fdq!J^SRztax0la zZ|8W7VH%L0x}jhx+8BxeCfTfkJQ`*f>{P%0b%y1riabj+in@V^ehpj=48!4wjuJyK zJf{C^_Q)+NwEk6&mq6?AMK6R1GVHf@-RMpvBVzQekc^5mw4eaF8KhaU( z_(n@#j>iQnVwY1X?&msT_I0>s>deCZ14+rd)=N|bktco1(D9y{2FHk=bL40x++X~$ z&C^&c?6k8x-?fLjy{+1Ouf569Hi665X$1}zbhRToyTolI0GCW}SO>2)BYg(UAWQ}U znac9YN9+L@S@Mx~f9;LgSf05RWRrYqt2Z`PPX6#zI^?R*rml047hq2yP#)@|76Tr; z>kF_}zsY-e0T5OB?qh*PltWd%vs<|m8?vNQd034+egDV}tDY+P8wAdxT`4g;lxL|s zSnp2w(cbGXIC$|uxcyH8JL9tR`RPBS+aI{Sf+Uu1^gsL2AqCZ7g!An|+fe3z@bs%j zsT;86$mE9vQxlE7DK>40K#>q8rS?00GGp}mh?`iS?E z`GgOz;sihFm;Kfk4Nhl>JmFSu0F@oq?surjmUT^JwF&uo-4^eUnRTZcC)wCA((1(M z=-4tuFr(0}qOJ8baV$U4j+}7?xH~IPi%rhd5jk?*yUDXo?F;24QjYX=p^4PaX?A3X z$pKz;PFEUzv*$IB4D%|M#~XV3Ei{zxea;bS9{se>XB+KF&7RXN`HwC7%OT}yV+;06 z)@gfrI0G|84KOvc$K~%vyS4PDikxQ2?ByR?CW!+mJ={kuK|0AppSwm!K}qiFGj*1g zD9t1(I8SW)7?R8sV*6q&F8n4rA_KU3@7`^Isf2n*V~b^V7MC{L`AoCaHl?X0{>A4b z4nC?qx>ZKLX0#L{I)Aq0e+OOi7b|z^D+WX!9aoIo=e=S^=lk9^x!@FkpjY_4PbJ)O zi^bg+@cab{=)-owjO(2H!!ED9pUewDNLL?N=;wS2OlgL>{wa`gD~mmd?$-pg6K2Tf9LYGbKC44sQl0|3pmS8~n;p9zEA!pwes#xNXh4L<9Hffztt3C%qw zXb6bh?WwAGPdcf5TE}mC969h7rG#b>g(E5&2=7aj!4q!G#aVUm4Lj&rn=|6o)tISB zBSsmd#GU~#98cA?ntB60Eab!QiLm0&n7~7j)K69_ zJNtIn!g0`}f&?B!{O@ca;m&}a9lP#Guk7{%=@r=pB=%zeBkL~qj5D%%c>u52iLEcA z^ewMf+x1&s`3=q$KMwNj;{A4i@oqcgr`w+18K>~{993tz!!ST+npjOJq1k_$BC4AS zFPJU>K8bw%Z~xgvgq98URj%`t5SuE;Pb3+|a`6;otaI=LfA^{#_ zUZO8}Kr0D|Qvq$FgO_dM)#kuLL)vUOb)IR1S|Mv@dJ?D|JHFC$r+0KqUz>RHKvQYc z3y|#OAIvraOnN{Ah9iaKnHX)5pAyrJvI$pMO@kW%ZKel>Mb>^al15*<(Lr=fw+(>j zFWz+YAtq0}QI)l>lXvL#mQ#f{Bp+cMk$dPw_t5cORo8mU2(zy}H}vNmlZ0JI5}1RF zJ1y8C?0=~ivyUu&tdd^Q%yYz*e07cq*Izm}xu15Q z%ndlcHis7@lD6)+D!Dy=vt1v**sd=hwwq@^ZWou|aXQHf=%uqYS!V$nYX2y?$F1HT z;0*q(cyfjMVE+)IJi3c64a(2bhG2CUIG)Lm;F@Fx53ZK=ztk;VNEZoU{RDzE%A(^s zj#;KqA{E);?zo`nCwrhxPCCMmjTxAn{T_uCn|{G+>MLhcn(ntYjuO)5^vA($M>7}- zNFyMxEi4k*I2c)yfm@w&s0(JrdRNB8k%xfrDVLZ=e(PG!Bje59`YMyFD>%^s_F#D0bLZe zyq7knHuXw78q=R|gH`DI4@<}ZD5?5NLX|@l=nzM?`YBAA>bcm(L!D`|d3C4AgJYo9 z2okhY9=YNtIA#=OroDpWKloGR8x{((u3ysAz7V3V@`D!=+fVFMr&>9A(t;P21?fhn zd?s#2cL!a!Mktql^cEE_taJmlQwA6rL(SOYq}uck(j0lrpDEL~oIXCVcqtow=K{aR zJaF*3}_!XR`ObOG6}yg`OimS zEh-03pHf897^NF+obM4hE|kl-Qs_`bw!VPL7$HLQOtvwQ&Hz-$C7-?WJoU0i z)j9s*kE~?w@^NCuvr7y0g>E>zhYj@>FR2U!VmnA4A|T<^(+6bKskfvQK9BDRs<+Il zxFh?7GD{u=H8@-@Kk~|J$QN)#+2wa-l1GbwC z7mqn2JGh3g`Yx4|p}yLpOp6uBTpg*AKXfv)^f8GI6IsYtLUe z8sAYFe;M7{vgA{$yw93Cm?&`Ev2+wm2J*KgPx;dv|B+>Ew3g$3={HDyNAIBbpgDb^ zm4IQn%QuGf#N;n^L#8t@Xqt6++RW_{PX}x`Jn&<^yl01xzm!fO`)+)0J3o_#GgGuCsxUyz>`NjXnr^ZJ;DU06z4e>mIIW&zHwstM1D&>0Uj&+Q8Z zx!AM=w`zs2?#puKM7^05ZN*#dIvk#0YT=XrO>9dJhJu5ZI8R+o;>iHql|~gtV3Ps( z*e1TXRu~GA1OOxoWpHG)NpcaK+JEAdXydJhE`_jH}Z} zW?<*6g+1fS<2|Pr_t;{v*C)jG15Ple4y!lehFOLTHt`-VAN)QykWvCVXzN{~)C0;< z#qp#0&ZGIRwGq%P0O~6Emw+Px?A~S?eanJXwBSo7?w~WtEg@n9 zu}LDo!wVV?0wtYJ9+cQAU;D(DoKGUDzm5@|f-)bukd$J$08a%kMAaKG{_q`35DfYaIn%C^`zOdut zv-oP?!QW|69!ct+SMpr*%%tmKmy99T#I7#jbH>yB@N~gSKOmY-ss7Cp1C7DjnLPEa zAaoViN?}=2Suoq#y1A77%+$aOotObPSq6ptq{Sz7HMFfqX20A4?DC8m%6g{LZE0lh`aZ%36q>|xVy!+(tNq>ILO0dOq(_#Dm)m<^oqeo!1NE89qNP6-PFf= z#UyJP+Kv}xL1%l)`0$QEX1(i#^{x|VUhjFS9q;tIVCHp!98=Xp3*eNsO92SZR|Sz4 z@CN|&_}4#_P3Lnl0(lbP!)8h#bahDr-K0YCn8R3AAYbGPJEVbEtAO8SmtkhF+2(p%@Cd`#4fU66AVVMAz7s> za`@GzX8kT7#LJ5^%%oTg^9D~%JV(w2Hvt}8AG^P6o^^A5zP)|_`F8%28QaZ^?d9{| zlhpo1Y|NO_EsG<|2WmrIJ(l|{Pk3X8x@k6T9S|tb1t@{54fLjLKKL<%Nk1|A{~A(j?ys7|^Se!&klB2RG({J4b~E2gXiQEcuCjb!*ci zwPmI74Tr`79sk%55P zE}Esc9o_X3|2$8FJ`2z)M{^ z%DY}d;(GXr8PktkCjS85yIYbO0n9BE&ktAIB|5+LQ6du9!TW>QJTkd%R;q!S$6d|S zbr%=lWCMq(lAZP;TMT`++po=lG8KLW5EYBw&QM@tNBV0|2*jRAM{dESp9H0R;p6C7 zha`r_?&#rwqw`TXLkBu`S#eP+bfk=eXA#2wCwWq#T- zfog#`b+GgFv&$6%^j(i2+26H`K#N%jLwi$je#Ty|o||yH`C2Dvv^b#Jw~iC<=jx(> z{1=Y9ZqybJ9wK)o-g5W9fx2WodX~b~s9Szm*))QLT!>DVr#?vvKJ@g{AXjTXe%rkn zZbuU+CKs%nfnxA6Kvcp&w6B|GiD#R^V>9^8tVqGFH92S&k4YZL0I~Op(V;U7n}j#B zFL*UTm5rA0X^lYs%7I_EPbdrzrW3-W(GulJ%D02h!BAbEz_oEtm;ykD2Q==b(HI>q zA^bf8i!Tp9-4PGY1CRgoB0Kc+no>tc5aiI>38#y9KiqJKJxQCz`U6Sq@b;RQ6W(se zch}pWe*b%Jz&~uCU+~M>UwpmYa5LkhKNO48)yW%P-Km3qR9E}W%ijL>q^Huo838}w zQE=3ofLL}KoO5(!?MNP4?oIcB0XOV-XqnEEKs zw2_4Llx3gbP@=Coca>cl2=%ebL`B`?qrNhg+(A387N9NQFr{>8*!I|!b@WDtGcf&r z^q__+M_zLHfgk#~ef#6~hhP8w_U5~P+CF=Jwtf5U7u)5_tL-N)%sk^UqstQ>H#&T} z{o`-Xw-=vZZvW~R-}55G-)_&j#ll%oueMH>Yhwt;A&%(A8)bEG0m6ijZ|sH)up>yk z;`^6D+G8`u73=Oj2oV5?!Y2G*2Z{$K3CDhIoy_FJ|0FbVjT`#iIFpuS)>(i!qAJ_K zlAn8H+45>Hkic;s1Lhb)A;E(qd=C%zgC~yd_MHg=yBpLwhOD7O!W7BqNjs>g$7#b0 z(wMyDM7dHhrAV_rz@m>2c;e_`+D#gBI;R{sP~QhH7$a}dce*K01JFkB(?Q7rc9?of zJfCKh+DN^DseJ$TcJ`LjLl)UjUE2+s5RpMZ8aCQ_@G>G1+#fi0^6}<=yM4o>AzWR$ zWc}-e;Qi?j-)*-){304VSDcQY@mLUdXrDgkc{qbk|4s7rktr2Y zJ%IGP!D-K9w}Aw#0VRSJ15W@Htwll9<2G^}#-KT;Lv!ebHSl6oPbvv=N~Z81mDRbZu@RlE+c)g_w=eB0%We9f z#5Gm*R|2*H7EN0qMh3OwarusFG}3FU{jQt*tSvc|cUn0XBQjQwd*hbPvi{KuLF4O* zhyzO{UbXkocGTCaPwt3;_ix{AufP9(`_-?0wf+9@|Ks+}*Pm^leDZI%&tHAI-QK<5 ze)|58+h^GR<;$1b4?n(P+Qvm|W@Zo1`O%Q8H>_=9Adeq8Q*sT|wbFC# zlG_0epO_D2Nz53aO^rR-lU_FAvtys=Ri93^-A$hMV{1zEOdVx|2>M~)>nIWVGSL+% zCJqp~IrkQVeirZ|=y<~k2J>Lt#qrckPsC)*y@~H=T;zLbc zZQ<*q{AHWXi_0WWa0EQT49z(WGN@<34WBw&`{gf@Lg402iuv45CZDP1<$*U2yuIN? zge=B_`X7Gv|80N!fBpOI55M`%_7~rLz5Sbi_wP9Re!2bjM-toNOZ?9drec$SMuxIx zMytXKA|yQj@rZ6zYQN@FY<&ux`4bcUq*otjK`<*B!~-JX2CREc2v54vJzQZrFIs&f zLxT<}v}M13Th9PGSDO31-~rrO;GS5NqE8x9z;j!&;76rFF&S>k@|Qlkw1P%1CSvub zMb*?%eZ)jp`TA)oB*v>QzkFCDywR2RS;MRcbwT`si)Nj@g5+&VlD8a%o1+I|_NG%v(`t7&yx7%OJHp)F0+4@sizm;$J)Y=M z@&cGg&5%Y9Z-A{jQ3*RsG9J|N!siH-7cF9HT8KDJERsF$fU++@7=ank_-2Ba-UT>fA z7J_SLT>tQQ|6%*vfB%1OzyB})ne+dvPUpaPrTDw#R?YI+Z5gVg}~Ks%^38ZfvE$D4r68dP_@vWj{_)6aipZq;gti$ z1U036W4#T0@$v_=N|0@rxpE-Q|8-{+Tzv7U zN>8D+L6C|q`Q;m8RbKu|(+DJH@g;-fOnsm~rxTAH+0JbVtLqH)W3fBe7ip5*T0`} zA%H8BE?KmWiLUy^Q+cgW^OUWBEz;KmWRKoDflXM8@=}B|J}kz-$sa4B#8rR#fw1*V z%HqaPe9P)0d%u(~>!0aS2Tes!|_cQ3wVZ3KEQayYXXH4ISL>Leh__s`gDjaD)zO`~oZr$;nps zzlqM0j0MN6KvWQQWI0IN3QEcbX58dE^m11%>jpLd)jML-F#Te3OiF=00^l!+!fZlp zmSwXIOAngTx9QvT;S!Lo4i>j|Fw`edXP+vimv#Q+@4yOyuM)31VwyA)9r$!A<6q!z zQqKmD)oy?6Gqlz5q393o^}%|RoNJl7^30Fh$1%bPKdI-Vp2cw6SRV$$04?h3&0Hjk8b%MJ@YrGS7{*UUbnWjnB8z@NvPv1Z6+0zR^be zKX$^!gOwfAY>oz~sKDL^{=M%yWyE7M#@o~G46K;vKs7I9WJWp7qQIzcDGffqg$-Qp zwb(lh>O|`ZMu;6f3B{g<(jGVY=m5~eH5u43w%118{#p{AF7Mk`GTfz)d;`tdk%`Ix zZ6=|7c>rr+e6=aGgoYI*TKR!zkf>To34WE;&$4W7f0B>ZiSCOAG?g|)Xz0Y~wI6xi zylThR>ELO&odQi@<=X~!>q;c62cB(wd_#a;AGcQ*FSnb2{{8m-fBDDl`+xd1j|Xuo z>4ghsXn*?Yk6bGMVf*LTzuSKOr9;28cG~@WF%GvXQ-QY-7jb zJ0Q4CyUU-D87nC>?k%U^F88zzH0X4uY< z52u%W3=({hyaNZXXWOe+UvUF6v4CHs`+-^5J<06s>XKvMYYt{7oK|w-=Kwv+3U|E8 z^^eytcw*xf^;@2oyyl^A$AKr1+UWSBlql-XcJ+bI)$f+|-%3J-lMC#$&x-)?0R;Ac z>vfXRR>p{a+6r|VqL(*(b${KfCcug#dDmw$8l3b{DeH^p(1C*_HZ1dy5r*2t2;yS+S zbOb<28n|1@StQGROsrETfL2gA^t?QLyyX<)9aj^7;%Bv9bD8}0Tkf>yv_U*iD?i+F zCFx@O;&UF}z4(^*Kb>!{Sj2tx#VdY-o|_vnD*HzgT-DEDR2KF(2=oCq=%2a{+3G}j ze55VntE=VN#&&SjC433sWi%FrlrwGQ$Ybs&8@!?xTFLFT>IS`k2y3|~9ZBMl*sk8% zpcd2+gI?AHi8S&j*5OUNs+Fjv4;%g(!I!!>K!VE;4t>$?py+=rPEOiQX`D6X>3@(B z#&>)>7$^e(W2n6sLu!vPv8h9KtZ*7Cs#huMFm)ooDEvKO` zo-=E^z1gm=oYtTO&UWohozEO2_>F3QF<*$Iivp@c*_Eb9 z7_vDcg0CFu);<)2jru-Yv(dpcIOwt3W=%sq_zyNB#O>5lnUcwr_B|41<>o-w@6ztG zlUxRZ31V*MGk8qBb~r)HJzMwOzjEZ3iTj_pr;B^OPPqT;^gWlkS>QhKn}Lsf?l^xw z9(W7q#f$C5XD_yk&!2C9_UB)3zxa#4f;sO?KWEWjhw7I28zJ~iF~~+W{Ld9G{^k`n zz*Jn5XjgBGVTs*>NU zg4e)NPWKY22@DKs_4RUk8s%SY8U7AG_LK=rZGoUz}o1?ERacwx8bIaxnX5`|8_ow=ci^meq%|?VQi! z#*c!4cm4i+d-t9T0Po+C$c~&!st@-S!n?T`KDJB*wRtLQo&RE^{(zgXd}Mn@QV$8e z*&9ddE=U)Y$h6ULiq*=VF%1^M2_Su6x_gRMfNoHy(9c|JpM?HVWQ@jE%{-Bs>OT6-+ z-XFt(_Btz7rV$NPQN;qsMc#r;{gnaQy!wSIiM3f2bjGAK?hI-Zsa)gRo*Mt9 z50jW>lN5yVGvOaXY zjE(s~Kk}(hIHmLk>hovZ@8zBY(mhKLKXS{&Z-4*&_E-P)f3y8pzx?a%t6%&D3;vuo zo;*WG?o8!%)Yms>+uOG+J-p@O(5L=LE=v_1@IK%HqC~Zk(|F1Ns_491pZ419!Ajl5 z)gmkD$j*+ZoSWPi57;9Ov~)BHOOBNNcIUy1|NI$vc;?G!z?U>!3;l;bbaVzV=}oTQ zTOND+6LEd3yd{N>a5bakV5Q{wys^@(%d*G}{7xlH26l=Zg->%?+ZoMMg0K<3q7h)6 z5V5C&mBDE2u6jCF`E$BP$)FHsLQD=MzzPQ8&JH@vt#+sZ_wm*Z%O#_Lz~!BM+eEaY zPSj0Yz-rc`l*an8JuhpsZSwV_2cdCD2iM8k)3k`eN@pHffPeHIU%Vp6H%uIui9N88 z->>{|%J}&gzu5lffA}A_*YDnKw|q`m2f29pYWw=zzusPb_3g$jV`Qc?%pig31A_rj z0k1Bq>WKoux+j#b=z-J`O`XNjUZX^PO%56gMd0{QHkDDHKcyrt+}4J)Q(lXoc<~q| zK!!&Cg2CNUs}X$Z1N4DGnMenRc<%V5p2*;TV8y4`i@ucAeVawy`j~)xz1EvchBn@H8k1V zrqA-9Rs2I=`Vh&?j;V_;0l4+A>~M59vhKW~$sZ**fFTB1G{WEm;|`GG!(;D1XTqMl zOft-IB`rvzQ{$)GDC&bu_}FP;`R8T_G{`x3w=c^#_9+R&_uu!}2EYQ|4w4d?jxl?~ zD~jF!B|qSLpsOwFWzs1rK||2kT!5JzgToh~r=+cY;W_z^9?As2#D~=7*%o(!$y($V z`g6EAz`%=N?eaoad;r*C+CV+~DJQRX*-=oTI(uyOR7v{I?PX<~Yl8$}Isr3DJuv&a zJGmT+zffJtwAz2Ydrr_j~#H!fBLO|@Qq%+>(4KsAKv+?YBWN==WS|bprRe>A{^*O-(RlOKZx%TCVeRQ|?~5vshp!5D#7>vN zqX;3y1eq5rocZ)lNZ}c%KWtBYUv}|6>nnMot@PO+FYPq(Yr*t+2TXL{la}7`4_1A5 zx=GF1i|N4jp+*)#<4}?R^pOQck_?Co6Rt_iH7(!QmafWLTy(Rz{p=dCf5t+;w^TfP z!QJ=JxsZQBV)ND1*UaKS@VFXJ%%aC7y!ln5lUMva+fUoAr=^@e9{e>U9=I(oh{bah ztN)Z!N2LP&pra_SgABw}Jl!n1>Qi;gz0UTj^rzo#5PzMbHtmiVj*uVu*i+;lsjI(% zYkG8);nAZ+w1p3@Gre?*Ct!FuC-62aml82r@w@BuG1MUTy|)9mH$3Sy5=>ssQs{US z6@HJ8R_(JRJB!{y8hhc*YSLYsH{P&%>ML^iwT62f>*>|4M)9uT%g_F7`%CV;fBXR6 zohupy&)GTmXpy)cxdXrFAW>k+Q;+VMv-9y!nyU^Y+O-z_GX0!2={k4dV;3~}f2^4{ zjm-wy-NQ>4?tYlMKxOfV9^+@75(P48gD4*@`_CMl_TO-wgasSrpKug(c=#SHp<^;96aBWQUX^7ZB3vJ zCbtfbAW2WVNlYRS7{0yO3qk(kR#zeAWk;Y|n{~~xL+u*t{Vt&hR7xJrSfE)+tSL7= zJ4pbfm1wNDhD8S{LkAfkG$RBlctNL*6zfzBn&{<&Jlo?fp7LkVg*9(*64C|5UadGr0m{O51 z{+|Y&dIk`zyk&p35*(_n`hAvXXzLV0d*W{dv9arPwLn{P0molFxxmkz{MgMGTihnTrsj9ImB`!}gYHRw6pKpv zwh~C%Yb>x!!LeV?0~>!%y>D0>Bcl6pLSk>JR-vQ+ksqJ-A~`nLOR@IK*9p=ej8k>N z@5iU)1-Fhq&Kiv*b!9-1=sbAd9eL>?FFyR?HCHORQUApiuVjKm{~s8?K8kiU6R?$F z6v1cG@zOPA?B~7zCw}ZN6N~ngT%fZ{q&G%TpoFv^WSV7x@A1A@dqb4{2A`YzIvcAg zi<84>l5h8YRn{N%Ofj%-WR=5gc4q#A+CLrdQ7&O}#PFxi9FHeg%}p9M)_FL5=>ted zr^U3D{E%b+fZshbmhpWa(F;0^HgzfKP4vXaL{c)zTOfdx-gPpS^pH1g&6wnZKGY}q z$zL8&v7b|0acQ>>-Yo9RN?LSTGN@3bi@~e6@-8+%^`3Ua-?~Q;_9vbqa)i8R&FkpQ zi_^$c?pr;OzS2cEquO1=J| z9Nd5cQG5!OwM?_z>1B_&31T=2}?Yg>j^9tF+2uE za#uUxHf5%(wvG(dHmOfy#TR;=O_tVXcA(P-cI0(9kGvxKfwixVcYK}lqQV39Gu~2B z0272JAIQ;1IV39ft`R!zH(4NhA;1^>2YPelX@rgL*(u-M`YuYePn*S)QNg~F0M+_Q zA{nyTRR+(_7VU<$v|YZUU0=8+A>Umle8o(@Hd*@rsd~>YTaGNTF3X$eVH!Zgw3`{u zG`X}$E=m9YtMpC7S>hsRK(zzVa1YOyuhQQ8WEOgE)x8-RapL$mVKOq)6ZL8Wgr2G+ z<@7H-<~$Zpd9c8Zd?czh^{g)*>xGN7_4;TAGIj9zfGL^4oBUh0@Bms*#ap(0%?gaf z{8u+I1?x|(Quyya0#D5?dy_+i8~uAWVp1&XX~<7KazZ`$m9hsVx?YAXj=a6dW$CQT zt^BKR{Q}u9*)P_!_1Kwz%M&ZkFHV*VzSmc*jh$W~GLPutqux~2SZeH{y8QO&8jjM* zKS=qfzgj7s8?;C}VwwO3u;&Dtd>>!Q-6&2MOdYEyQos6-N0}&(AO2O1a%^?GB`c7j zKPDZU-rq_j2}Kc)pL$r%aukZ}6?BI7c11;36$!A8oM_}ZD(M=dt-#Tmkq1t6*aB9_ zfeqd~Ii5OJfrS+h95BJPZumBs_yC@BKk8Ew6J$Kj*LoVfeIjydeJfGl${So$$h9ov z#1Ay#Zws38r(R~{g^qH3^3wjwKeWU1{1k%@pph5`` zBO6pqOf?d$3DHn|lT0HADppK+!+RjWC?dWd{w6&+(QMJay70>sSk4+PSdTi^g0-qA zl6tI&X2o&#i3uc9eki=(hCv0h#|{IM4B`+8)TXB%-l4LhL5S6l^b>IkU7?A-&<7^6 zVj|(AX)O9%emR{_2uO0Yov1TAfj#-{kNgdvc?yTkq9CuHd(1ygD!+J;QCyawo>ezy9AXm$)lhYwV{*@{YTzz z&@pUO#;GbTIBn_H>BtvHc^*l4UhI%3lxL93?iOqg59D>6+?zh99xUK#|<9v z&FJ0AsYFI)HOl6#pE*~tDru&7z^POesdCM$kz0vxFeL3YW~+nwz*&aEXEbISh&9iK zQecw?(MF{gkSyQoS+;)f>Dy@ChHF0jMBgJHO2V1LfJ1!Ss80WwI|BxPmC5U)JmK3i z&;jRaXm}z>6>MM2c?>2t(bl)@9&w)9MYI4}IdQ_dwpTne__ixWFJf z$&FhHhW|U(-@GfUDWjbdZKGy=O16h3= z3RSAG)-i2E;y8=%Get2Yl?yNaj_BO94z?izC=|h1%2^o`c#>qLzeQFZR7ihw?n#v= zZl@&H?lviLp)d7qut<(%k&J&^{^&@Zg3{EDWxn&K*scha7@5F!LCHCgjND{5Tu;>$z3^h|TZh*>NN9J{N!3 zZnNy4lF#FHuDL3CMF6{IQ!Hy_`m?&sR#a{YK_tyCK|W5L?CQ9WaPXC8*U?m}G&ljX z`C-dyz{N6k8lj0>{|hg4SNwH`)^%;f4?#?v(if)+BwUO>NJqoAwn{w%6yR1hS);Q# zlCKqPRpd{gSQ|5qT}m2AZ=ObsGmuyCBJ)1D6ug9)I&ELjFb1JUOr*oN9)&l*XyXw$ z>h^`xf{A4-Z408m;+!Jj6C|acX=PRx`Iw_mO^thbCl9LBr#$Zv2I8wgt$AbzsQ{@eloFlW9n!JLB!hCIyYHnS;2W~rET=(+1Lg|PCYDlG(|9!)6T_-PVR9E z#&-+U!Fdkwn0TdMb$%?XPTC8Xa8A&haA39=P|HBDDzhL1(VW5t z&t>}*ieiDRLD{&pXD~Dn^KEgx$ilT8ls#)(SE$Ihi(({mUplM1|F!J0E z(MTaaPyhup`l1eHZIz%5J&?8#I`O{Zq^w1L%I|5!t+2pQF83*Wt7m!66BqG8j-@$- z4=cRk(a=|nzOD&gI|MX$M<~Lc&&L;16j;NzYfHo)$6G6I>0Sz8lRd@{#%3-K3P7Y2 zY-@w9^@-Ifdc`AS@YT`rBty9@lWq@OC+cjir?4jRG8?A4Oe$!~j^A`hfsQ4xAO!<~ ztE(0!sOiss46r)BbS#~ZjH>*>9r6&Y#bj(W3vkezfMyT`k1av?6`d-o(L+aZQ@ytSzS0D?KqZ!yPShxO$A0@VnIooeW&&sDKGkHO0Cu#Ta&>$6gqe@} zwj+^IJ_ay5-Wh!jZ*ruM2vG+|BEA${-&ph03F&GDZy?gA%^QCmn{l@43-FZ|K#jzQ zPR^Z;rD53|EKQf3i^BB!Na-mM-_HsskNP=eL4(2?J$c&N;E@s5e9|d82>>-4rDtIB zssblLaU!bNC1Fp|x%8+o>7VI0I+E5Pi7%}-CMSQLYatW#H1L$P{+2dIDrr**{-q%_ zA8?xTJSgaod_!Llstk}2^4qzxn9aF3OkfNH+|&NqtEx&NziGH zn&dv#FnGBm*#%IqJbKGc`g4jMPMv^1qK{)6#d5lBr()2T59=#`K|}iSI!q-lG-}w` zMO98 z&W3XZuAxLuzG1*$>CMzZ$_TY*+V78G{f>Kvs*nCJF zt4JhBby6jgoBXE41HBbQ^+`uEI#ituNi)n={@U@1!}U#o{KxLnuVOXE|EZash4UX+ z6C6j!q(E5Oj*lymqcM15^Yg`9ZY|=WnQtHOEssyVFvKUDT25MHIPz~ZEPKlMOCZ^> zobN`DGia^;FPP_MhQkc7>r>`?>g+*b!J$1yRJh~ZdJ_;QUw0P3zf=%_d1dnx=YWX6 z2N6*?;&c>VXbbE8!QvEvoAp*GQBz|LYW2dM!(vbqX6zomF^rRti^~J*+N5Lr;A20>}KCkXLo& zFkIw=zXerCO90nUgtbc5pqWC3zIHOZwIzblEw>F_GYY%q?LybQP+^Z@wY%;kWUB^V zKHOX1E_cW8{O|f+z=NJ*4(J|PRAd9+4Nt1*twc~suQ2T6YaB(nnU$@p(*X1m{(ER* zdJ(Pq_k#_nr)~rrC6V@AfYmwFgEn=uVbdtt6`rqp?!G@xt>igS{Ehv?EujCNh=($p zP5}s97J9IzASzv1P9}udwkK}xCCJIHC5D-sN}iEtpivAdP5>9 zqK`)SV?EU0Dp^rYE044-l^<~X^i`SRd(*SV7@<&b%QWipnw!ASdC(#I!Y;48rxJMe z2_nVN2HNM2r(5jUi*havGKNt+b8P0C)Q5R7}McV00YFkZvu-X^T>7 zkkP5S4yHQPb()vP=n2C`kegt{sK6jmT|+dPWXgkaW*?-ieA;UOP(1fJdkevq}^o$?$>qYOBUrD*Rhz1R;p)N?Lb zzv3n#f|$?t>RDMi0Mr0w6X2dBZ|0nG3%N33yd3N-O6x-4R(3S z$Bp_jI8tW0)9C_nLYhrQZA(3;;`_-oM44gL@h72xakK7XE7fn2^kXIJT41j#xTNo5Pm=eyOzne$2O7R-W8JHSGP^V*g;R~#vB?Z^?K`Y;6I z;mu0gOi2}i0&))@u0vJO~!;0KNdWgXyJ8`F_= zLN5vkD>-l%l)C3YJir<-S03)`Q%63^wxYIVuD{weJStru9eR)ec&jg5PEuX)Lti6o zc^*@O1h%wCvpF{MQi%#o#xxA%*Qf-dZa#-djzyrle!&&Tug;dMZ(c69P9ItO%214= zbVP%Mm)O~VzYuzO=$yBJ43oa-!KQe_bHjYIk+`j+P7mo#cYT?My)8!f=>(3@iPhR|lTtp)D*jwL$m4Iz2{gYoOYp^1y?BJ6W z3@whhUV%p~c~kfVx&Z2ka%6C}ugto@>$i=xs{|$U_&U1Nj;S5c33?o1XgW0t!8M(Z z3i18}j{*dyYG4v;wNX(Eqa6KQr3%@!(o{yo!BDb!etIgU2C>XMITY5Dy2Ctmbmq~e%N$Q}K z_&u+NBOxB#mzogx_M_Ur^S7N2YiZ@%fKd(l8#ry}iD zq+5~N>+z90UEe^W6L~h6DA8%uLyiDHvR5Ub)I(|UBOS112v8kM?O4E#98Q7d`S_G4 zh;rlc9nbvnM&^s_b6!ZnMzu(f2M-CRMkz z?~%#CTyF*=@_|t9kA*xKrK^Cxur`5RCL*(x2vZ^>G9DH=uVXRKb6wI=*32X@49sF zp|-q9x2S{Qf5|%78E^W2!Oh)I&mQxfu%qSdcpl^A8;Y75iWlQ*-&NH3bmGi?-KcJ(57@|HVjAMWi42}-<-@tZoJR!NO$Mmm zBx(j<`7w#+-P2`H?=$XwvVv2iMsU;{aOwizJNv36E)8qB&j6Dj>!FLh(gv-pK!Lx8 zi8Utx!x^}=QIBKE`BzKJ4z%5}5s)KRgBDCELZdj+@dm6i%!LEf;E>5a@;~Vb+NP-| zVNff!^n)>ruPPK_z9bC%5=xm~GrB^zbz*oQyW!9mJI4uNdu+f|*LwzL=TCi6qyNxSCf><6XC4tF|Jqk1>eEx?%MjDKNW}+n z{yoaLryTnPS-v^RZ5?QBFu9WFGN?@ITo?66e8c$EB}+e6N0Gn=+Gupcv2xEP{_m!c z=EUmSA~+qL=cpPeMd7qTp}gkV8J@c2YwR4Kqtg`GFlgxLc7{@Iln|Wd{Ha{4DY>|! zX-z&WK6?Yx_w+-oX{0-J9GQ8;YtCD}wK}ls!_kKl?mK-Y9X@jI10!_{a5_tzqh#>R zs1gS1_9?PdUM0;>SL=kAKb6a<;FeK_PlC9ku{yd~t;R{YWDwL`@Pc&bcY@cMjiU3w_Ah2DZkhd;cURwgbE( z*k4}rpv)VdZm|uuR-v4}OS6}3j3ebwjY1{rNBX8Im+t}-=M!i~f2j!|wWdV==5E+X z4as;uj_x{(7Wk~_hs?n{(8jv0X{y?$>%gWN$&4l-RS*G!EQ0}0=qPQr4x|=Kd+rDb zY#9%D8JQr)iTRGy{9BDAxN6q8eXTB@4MFERDRo5AS9nS3&^R6^HE`>RUl{9g+~jDg zq?8+P(We$ic?1;rkx`g2xViEr&VedGC*!~me#+Fc^i1}n_Gu^@lNNo;l;)ae+WQ=H zA0KUSSmQ&E867>8sIFl()U2N{B~}=wVl_g7*{v{CRsf$!5`4h{$v1Yi~ld-Ki(PZ7Q3VUD{#Zif5%Zs`bAVEYDH&)a7o)7}wl zV!lgrd4T30LF*Kj$9@q`JoW1cL!ZV%q81Q~3{c0N?#%XtRV7B&L1?41hy zNf_KLFPOBE@k`*%7e&gaAPD08RtV_Wn8lysNt-j+rgb_s%ejp%q@nD)nBOOlhYq;vohfJv$nnlKI)ag5)(aDRd#=x2Fs-~`YU2}V zUoJm=|JUX0)eAQL62S26Mq==i`DloT4Ab^`oY2A1F{3FqWFEf9J+nNPh}&kA&NfOd zh)LgrX-f?L`V3Gb0eSTyoeS_cCKIO30*gDM;|)f1y7^JxRN zorye|i7a&h#gHG0Q=_)@W}u0PGNy*BH!pB4uA#T`s#Srt|*{Q zbQPDR!7dnOQm6!5@kW;n0~V38`lRpSf|n0zD9pN%bu*x^9yb^%h}f$VM+#M3!HYwqrsx1`AhVHrT+75M{jOG)R`~;0FcKFy4VDGW zmA0cRk+?o5`tYp$Ru(SlPyI%DmXqHX8U+bysnzJ9i;(GnK&h2K7;I~7);I8jF_ zAL6D}oku%0@As5h*WGrR#(CvzcZa=kXK$7_ufAX2{PgYe^7#+TD_U}GWEH=I-q0Vb{GPhox>qlGJSTM)5V@Xi+-*ht;)w8qc!Dn8fK$1 zu;l~_BA;{A$L%2HQ3$uDIHjj5AE8L7b5A)hzY~83OSTn8{evX~RZp#|L-ROT*~k|@ zk0#Y2Rs=*gP)_;kKmU(l8n(3J@W(V6)Rzkb=DvPP{Q?{1SORjC6AZ3t_!sE#?YraU z^*cIBI)q~{qpB+owMl}EvSikR*w|kRcA%PaKIAZH#TcrRpoWwjZDMv_>46cI9H@~- zZZU}+3Ur)M-c;Ii;PfRoWIGm&J|Jxkf;ndhjhuxY=I{}!4L_3MgQCWeQsNXvcpmcu zP;jZY0k#p3%I1^KQJ_Idean!}BjOn)=(HlG#IJQ4*^$y}klcbzb;Xl;0@^O@-7@kQY-MXpV(G)dvN9Mh?@| zHX~n1Cnzxo9l*+b6ZIW?)p+cGYh7+uI)C$O`R?m4m#_Z#JfFP1}=3S3m? z@$J3H;=~5Vbn$?C#4Gegi(w?^zPCH}$oV+stMdm&IG$F@OD%W_3KLVD9#I)>KopVb z1k#d`6%dUsXY8gNKw>aC$oBgLv1u6Q>Ev^A*N+(g4Cd#XycaQ}L_re33&DW$ZR>N}qy1;G7=HR~LTNhr=f9?`=h| zn6m$fK2JX2_9A*Xr(%21u&K#qKfb>~1bnooII}P3_aiMK3@(nWY6IvD8eMhg>Sf|l z*3=OEpobrhqJ>ZtBuE`E#t1Jy@poqlX(Q{huAHk6fl#Zp5D4jt5h z&9mIm6%Hz-K|oHrieov7p3V&tYdtygDHAh&1Uv9Gs;wM6GXew7(UT}CBQrxJ5qKfZ zk4Jff{%Y79!16|YmI=gRJNGocz%LU2*W+k+90U_sU@?`|SrV4cF_cq1*}Gb1k~)U} zx&RvjO0_rgosYM?u=IxS9RX|{kunW|5mmlvdA?()tIk`P2d{f^#$hGJxjM@Rp^&95Lr&KmObQSbq5SkIMxw@8xCF%Oh?y zJ^{uph)0|sQtt>ovBC%+Z2TqvmWI(LUdpdnh`zYo>#gO-J5PY+m2*5wW{-eub7?J$ z`aVWkul1tK$m7wejm+Ah&vwU zCVO?5x;A|F%x_`$Uv7TpS|e0-Vm9Awcg0X zqsebhmv@&BdAk)`E3Qw`H{px*NBzlWBc&RCtSa~Vsmx-9959fRUUefc7U-K*f!9>B zdJ^dZ5v2t};0<858RPKgAjkxE>%M;AH5!0)ZZAGAtlhvk?vaC=^3aw0pyc$BhGGUo`fopWF zH7MJvdK`_IGBDVv*{}=EpPoNozWVe3T>jU8|L5h~zkJEs*H4VPu9wqeURc6TeD^Tz z%bUTSU~tQYkX_ceRBcLz=Uu1c{Q26sH$1NB=po#r;sZu(ZTv+SiV#nEzxJb^_R_EP zznIm(S39aI^nzP-1WE+{lTtTTAU(%ABXG4*R~h+hv{N-Jg#jGOZ!LXg@EN>jRJg)H zcV;fbcV*zAGIds-tQ$`0%Nk|YlW9J+A7DnXfl-jTV1ho;4=?VcB5y77f(ehJ?-W>H>Po8}R zTOaI{ssX{F!|A4P=?iXWi^;cSQD(H$SCTF4EJh`PL0O}(^Y2ndY0Z2T28)dbA0kFvZM`OvN zvZ|xnaCFP~sUG=`)`q_2I$PC6L1LwEqZbWwOu>4tRSGt6+hV0kM%8`|L>j;f>J?e( z^T9)oqbs!E6DUN9sOrKk2aQlg4-VJX%+FcDq_5U%(Ex2MIh9r=U;Vhjpb?S#hA3Cg z;;7EaRMwsQ;erhVB4nUvWuK!iBmGN+5?f<~zg(9qoc4Sg7OSGt{s zWw%^JaBOsMZ<$vW=>s_7kthvN)@aR^%zsn2q+N9YlCdsFDq3eoVjR(vrP8@i3y~v& z3w+vOqcdsq;74Bg0Al6%GdZ0|DSz?{<;41fkh-E+m4Y5sjDY189tyAHqZpb5b*8~zw@^rfbZ(%sq!lJs#lA*gV)1qH1xNqN&c zqH+Ybz1Y@r^${-dg^MHqj$f#@{nUnV3CqaUFq{<#FmMgUalD3Mw2&jhO+C9pG4dK% z4FLuhW}PaU22S{0rY5yN@+a5`3oZi&<)Z1*c=b&KN6*_dh;+{6NX7Ebt>YI0p4+zN z349u;eCdi;{K8-bkJ1uhmxHO4Ln%4*0HR0*^N+~>s5@1h5j}FQV_MVR@zymM8js%! zX%IS|`)))9%QD8&OoR6^r1>!7nwDDkCAZfx27l@f-2JV2a5oMjd%$aOXN%T%q?P&Q zixU4;hU*Jl9j9@!zbZwLU47RhS?6*Jr_))%BL{CEVs{WcuTuzABKzq=9!SWS83tZI zI(Div-?5-U3S>5bDwD4rJ&}!Q8cDgm<-5QBX?gkM*WkKdjvt>ar`q`Te7WRZ0jJ!+ z?ljZj?6cil3t>d-{xk%KM`VBpw_iA7Fa#YQAjkEa<=w02z#S|PpE{+STeB{?nBvGr zyFi?)mE#}s+S_d~R1MwWD5JnAo7+7K*x1)Q7N|u&rK%16G^kTS$ZN;6$sA0|0~WgK zUcVHxC;BOg@z>FEb^?TAb*ldhw85a7^gkpj5ug6eNx?1}Q$KGb7#Tco(Uo+yot>-t zykVr(b{s}xj>cZ_y!?|rUYyILrtj{~my0uQq~zr@hpZnSqBFfrn`*jbb(Az>a|#U< zk!@MO$_G^pI&wWr8|G%}Qopq>j$(E{)>WUDkn+ zYZ#R>qc9L8JqB8nWoYPpfqE!B0}0>LNhL64=}Iek0s*(Pw)6-c@(gNYS1G@x?1{#VWNut1ci8GQkq=C$$AbVjxD01GMO zhs_~+(v`PZumE`ZBB1vubDC!o|Qn zo0h@}ObP4feqR{0kh;$_UOE8kxh8ma@tUm?-!5-n{{(V2uVOoc(LTCp=RLv7jkmtD zup8V9FtbT_^CdUBUa`(NBLEP?!|m0(<>LI+vd|!pPM@*Wf_-7UN70}sclu0T!io0p zR=MOvhYAUfbcsx^m#HlquDb3|U2^7sAMK5t1`JD5wP{b_{XV}6=+DSSzJGC|uMX(I z#~0|+CH6Cjna5w#EO3ieJqAPB#+#s)u2toNGdfrO4~Nm7(qw~AGR}KN9}=VQ%(g3L2b zIobkF)b<`jVR~p`5Lj68;*2B*Ub@wjOOk88_@t{-bzhz|EHg+|-$638?5TpYGNz1< z$)RDLsgE;SpKm;PBtt38o6pJ@C*2cQsSQflq@^zO={;HqmF1P*_JRekvia8S>kFny zZ_j8<7reBCM(cyef&V$sNO2m6XwpCDjxg+13_@{7VSyW_4PKf@3(9*vl|qGAd?UZk z>N?Q$%A^$-U5U1UXMO%uTP>0s>1FTcXT7*LbK5nn* zW@hxS!t`ce3Ou8s(Qknc9$Nd+|CDG&Nu~bv7Zqjwq%7r1;hde8#@pqG{(^BjqLa2y8pRVNfu%L)=!PcYNgm=h$XVu1-(z+WS_8h+p-v!=Wt-Nf@?x>){;RcFd-H;WBdONg zoL55Aw?Xp&(CA*AGfiQWs#j@GS;%su?hdlcgFEc6&e5OP*lcOTu$g)PqMJNi(AQVJ zB=1&=0z0sWtnJtcZtl*Q`t7p^k2jigUnO_`EqjN@b-u|@j_X7=!U-tQ$vmS=<8sKxn8SziX{&wBd7;I5E?kSk|%|Mb}lQNs0FrIrdNOKVg|v2TSF3MP^b_)cJ?<0#0Rx6SKj z_VxT4d^gxvoqBEQ8XUf2zG;bz?cN*7UNA=$&93lKJ4$!+@3S{-hp=~rzM)u0(n~)$ z^L|QSWGicPWe=v2qnpX`9r^DAOa=Jf&MsF&eSA-4+E2KwogNB?@;4Q0*xfVhoC~;u z;3y`P*;_l2XNN1M@wXxEZw(!KR-3grt>+)AQg0+=(VXZ)KN8|2qkAoZEttrg0u$6{ z6h+#A4_zSWSM?1pj<9(D?&8t%nsu*thcA}L3y&poom9tj(OusKxqX|I$JfA()078D z<=A@LGwFF(J!R4W#a9=gHkA1_xNY*2nkl8(;NS~FzC)uW0%_-@Wb@~L2j#TKNUb4LSteRdpe{_b$|w^6BuVESWp)Yw`gph0SdrIUL=GYSn3KlEg`x(#;jLO znS2=psZyPIk{71}2~LTD41+klm-p#SO~|Jel}VU6>gY2%S!Gg@_w`7o7v(2&(-0{? zgWm)=3>;RJ1`Pwv@)d@M1?6eu9`N1ru)=Aars`G$hq%@IXx5Z?){lk+Kp2hg zs9=n%Bg@CiOH~||*{|aooX6gZ%JX_n643(yR6wi0PBj_EMBPWe{KNi^(k5Tp!=&pC z`2xU-S_cDsJ^ki%8W=v+kHHoyLJL(L84&Rzd;4eeUH$Hp`or<26Vf@l|hsAqYj zXO!ZTK;(W_BpHPztaVi)CEAme0m(p-KFdIxocII{;nc3vzv=L_hPqOp0*hZ5AUUL= z5|nY&|NH1e>v`~`T|5z2a2@AVc+HT1bz+A0h_VIz!y8JMcV_|W0WG2PQ=U$7vz)PJ zc*SD=-To1DeJTjX(g)Y7@o!Mmq5KCQ>Q&V27787>dBqIcqHj#WZLJA00UvZB(w~aB-ih(B&>` zJHR!9kiPs-MbNpbWF90&o8;TanCBA@_Mz{H?*_h| zTDXSe^S2tY0pkL_^z4vmi^-;ArWZLgPbZa5wr9FPuxTw#dcdmNMuy<(q6G}(L&*p3 z@F@-SfoUYFzHeBcBFH~D;fmLx(?fy{7d%c+*#2h!CoF5aFmug{;4d|;*z((kSqF}t9@O%9@%=E&VF_<8$y@q zZFJO=)2iXn5}l2#18?u0gIA(N>Q=laF`jZ%jUO9Y<$`m9d zE1;ILfdZU_Ir!tmVCL2Y6)1y(Dw{%U3wbw5zUdl=`~n-jDgY2Jj$mGuoJ|zoM=0$v z|J$LL-#>l0Jbm*1^5Y3T{@J@_?*R+-UU`rsr=0Q#Zu#>{X`K?rvJ<1X?c9;bu7QnP zwbbJ~A0SEj-q8`a9UUx3JbCqq07hg)cXTR@*u0{-&({7cXk2pxxudpSgEP9lyE}?j z{>`YQjlJrSDJVZ<8+9`$9rYD+#=obC-*l&zrmV{O=cbc7%-}4;{?ezG{;0jhHy58) z+$vwc1iW^{d4Ypk*+@!jd5$Wd`iy>pTJlxP(X4;@Ewb5gZn%r#Zs##NVHCz%*u}X| zapiWbJs&Iyey5OB;n0$a+g1!*MIM7aQ+(n28hloIEyBx}Wd%L)=yS3nyWNos_=g0Gc3KcMZ&B@kd1X9_6sK_*q6KHgq=sY91 z_&NFETpNZaHNQp?oQ=s~Cv~zpYigpU3LG|W119zQqJUuG0i1)2dh%etbl|H1%Q452 zduf7lRbOyAiJqU^0cq1^d9ac^VT7Euz+um1wm&45L)DnYAL9U&ih+3URX1_F z9>!3J>q<*|`@drOz+bs+2N74V8=5o(P?1SkUta6=Dv)ZHCd z-5RZV8JxO4d0={RR0_}Xrk!NJE1r5MxXKTuJ*_ZiTYz#yE}TJzEJatp0JW!2NDZJq zIrP*6j@rY(?q1VfZm2$F&1?7IXu0Di$Im|df*ZHLTE6@01?U|`saJ!h;i zCGo<>zP<=uH~w}bIMJ6^Q=n&`t$)R%sUIv4PEVG{AAGPpdHi5`c*+Z14|t*L-U&er zUFazAbjccPm+9rv;YE^i0YU#UcsU)BU{|(~TNt8uLBBdMnSS`;qkk4il0W5)U34-} z$yZxeEd!mH&ObH8hI1NLFh7shrj~k@sP-QHQw`^Xt8z72AACoYeyU=cZ-Kz+qdk^3 z0jLp8QT&uOLmq2>e!N^VGQKJ3uhUh74$-TPz>ajKL$5XMVHFPo_w-2@;AApmDmx#2 zsP9@Q-z9BD{%s#lFY`C0N=VPup+vBF_z>mu#cBc*mwp zjxHng8a16+I`LqVnxq}gXjmQGk78TqkiD1b!mspQPsx1?Xq<12177qCYqwSeuzVOq zT!_74bmF>?kDC#~V@dHV#ZeI8u6Tpcg}@E?A^ym|T8<=x9~n9}h~c1DZc=Q!%f z6~qPN_;7jQ0G-z+FupwJ59zJ@seDKl7%g3MpYWeW}Un~!vJzZ{?65Z|| zv##T9NIZpsEgRQ7_;80nZ^N*$<w&D_DtB0~{m z1X05gn0$CqLGnfdY>pxf^7ol>_cK3#STwPXxV{Y%g)XhQY0`;<$mb5pdVS0D!VvI8>}=E zX?v<`f(_=bd=(N@_KLcg@(e$5rhap5f|Xa)tf?){7E~J$AQU@VxPQSd*`Wts?OlVA zhxCxgqE|WpdE_EJ*V+yU5{FFHj<}!kn5ROVuoM39nC8K9*P(U5u}?3)d-py!ZNFiO z<;Ugii$5;keRIy1lXC*z0qbLo(Bj2m87_4?)Vg1xTiJdMFu+xeix9@XYW z1#Hvfx4A-kjlC#B0W4-aC8y!QdWzFG^AsCDdFl!Erel4ekM>2+lZ(v_001|-qr$v> zODyPJV<4AhqB1B6sf;B1m<&dDR>Beh06+jqL_t*hSzw?v2V9NFwQc!RK`w+C7Ch5K zyk`N5WLgq_q0qB#d2{C@iVXCwKiXxYYw#+2z75Ux(cROboIKZ8^&Pi4Da9UiW`!T?@mgXi#GYwoGG&J?*0sv_p z2p;!(6f||j*;WhTzy~kJwCVx7tm~;HGGXydeqJMq?65`;5hP8QTA2_ z59P7v=%ItcD?0+ZiB(0n0FF_-k=w;PqmB9*xlU5P)0xex55&DB64g%-p- zM@E;ioh~&^q_JJ#DBA<-v_Wr`!P$Dr1+{i@#Cejd%)1E6=x3A^O{=>;feeF(;dvSr@{r!6al626u<~t8t}i(Pdcez4Pmg&%Dr;H>qGL9Gu_OiDs$;G& zIvqS^3i*r2zgr$0zF8g~T`d3d|NP&}_uu`Qo%yej14;4r8)SqGJnC1orQwFfcc+zF z!pi~gJ$i3>{E#W*d+#lu|Mqvw=fD2V^2z7FUOxWiuLzX%PmyMe~%BBK9&&RIXE3gfN%=f}?=dxiG7cq&fb7vJGJDqS-SCh`te}{V5#Pgu&^>i=zEEk? zI!dz0(8y}fiU3E0QL-`<{!dK0SSX_dGP?;797DF z47ladb{?;!m3V@pnIpsYa$U8A2@sqj4C24?WJqzFp?npoB+X{yCxi7HA-bVhM{-Y+ zP7CiU^V+p8@CTzH;BjQ+FkAyS#k5qXw;b3uW7+}$RtEW)w*(2M(e0fTNyh^XK)V?E zmPU--fahMeqj{wlO&(hEi2Lal=<%;0Bu1;Huy7%&X4W2)f5~ zPx{wxm`%6vg^a#n!;PuxY!8`y2%!(ad&DTqKz5AXPx*QL;Ba~J=y-YffXB0NwU4!Q z`!L{@ko{RquZaW8)5ip`_Z}=CJo~T97oWVheDUeW%fJ5Xe_#Iihd(WU{p;7uC9hMv z;CsV{SGQIe9QPQVIX&O;sv^ok2_l~Dtym`yWnce6< zyX=exe|O`ewig2wh|!9;u%X*loQf$QS=sas+qAU=3^r;i@FjTN`kXTF&E%`!v{r>q zZ+#*vdd2@{?Uo><(eypPpmEtsOkL=a4BuRgLOs+#XD5fKz$>SLOentab;I-3bb3cI z?(wogRtG<^W4q-Z)Vl-21y_M-zbt&P>M=em{rn$)weu0>1TBE5uZ-=ehqh4%Fpj`@ z#8>6rtK#kF!hYr(i281D!A^Yx9A59L3)SHb29C&+hJLMTl%qRQ>}WaJzcV1A0E0li zu>oUvO#upFOl!TVBMnJmZ1f;7V0iRT#m^dm@G1cm)!`gC%UqiYmvt$RBjLhlJ-^_x zX#E-bD4;q9O)H4UqXewOxja~D<0CZwNSgxl2Yka$(bSRVK=O;C#tEz{60-(Xab!!=&^AsE4e60%F!hljU3~@CoM=cvV*#a}x1+C31KIRa3=e$z ztFIDsq(EfD!L*wrRZN47qZXzFlsiJZ;ni-AVhqCRV70SO?#Cw)=#;xWTi$i1?W->s z;Jm1Jhz-5L_u$%8te6iJm2sP zs@>(czx~zn{)f+&|NZ;_WBJ1${;>SFfBA3A|M%tpTK@9Y*UJyze-EFIx){5F_W_Ia z$0r0xo;LCH$@;Jtm-e3)9u|{MFm##~-<{aKq!Rxtro}j|%||A$NM3qFXVrx5~4# zLBZPF3pSVM-gR-gocbIyq;+azCt%YU>w9xG)a&Tj0l)i>2Rkxa+oz)e$b9{*PF<~8 zkJd^SALy($5}SUd3kffv2LnQ%@|6z+Y6AIf-}+nf!-M&v$v+#_%?M4~c(uFj7QzVY zP>BD6t5rE!-M98p8q5YcSpc1+@_(Cz{#`&92XJ-h@&>&8c-&YY?B+{l^1Wc=x}uY7 zRUmq0+vGQ>?oR-+B$UY|v<9#d)Zj9lfx$DxKAIfVGY- zBRWb-ny<`NUK!`oYQre`(d5*!HBG|g`Vq%uOg-ulBEOi-;S5}o;_kD^|(MJJKoK6V9?@dJ@IYBerX%T z>v`h_MycSs*9Y#|BJi9NfF83D|C`_ZX8GZV=REL`XNG3Z+>_R=Ry%A}Ku-DQ+sBgP6*;29`}mb-3t&(~iGxqU5E+Tk+25r23_ALHz5|kCp*~{N;T-mc7jgQH z1__N1YV9ySZ=10gUTixtRs|_k*D5H7AnIwrD>l#MLz3ttxrdf{U4;D?44qDem7kw#Ia#knd(z6G=_3h)YpuE%L8z|%d2oKpvQKOV&G*)$IE zSTJP+<*CXBLL+M^pr(gW^>BgX&yb%fs6kBra2kV;;07R_Xo9pJ%O}C%jilQ1mYHr( z;)>q2&)lcyzw=>qFytebC-|^t!>Gi23lDfhvzu$ZF?!F&Y;eO9l_M{7ok{wtg~p>k z4p`XLQM&2}tW(4%kC-ApK3+b0_HcRsJ-$zlmj|qQx#L~A4ZxUhl!SxU38*u5m*Uic zQ&Ac=yh-pHeHeI7hyWkF|Nipm@uTI7FMh>ayU%z?;oIfQFL~(UAO5(!BmLXo{dW2M zv(J}LKl_wv<1e{T!qi8)=x5JM|A^yg$aivTJEyk?$ZL??$KjMaHlXW`=WSV`+-adL zbI zD%rZxvw`{ncGdVefY>G<%9NrXGMqNZb3u#-ZxE$D3_8x0RgEi)^}94KEu7`~kKZis zxT1D;&eJq6&uWlfPdH%icE~j5kehP1?;Z`*+jo4>-$SNhM~rq3Fmij<+Neq6aSejDZ1WwEnJ=Ng8O zycgL79dKyTWL?iIfPn{tI1+Ihc#T?BHA~in~$i!*kAtVh;=~=y-0Y&B_GOT$7fa3{AVk2IoIDTu>A`yP6HUn zf|d?!gDQZKS45x&c{*4xmcUSEhk(5HRT*TeV}U+3c)|%q>$atVVk>r00ET7}-z)L$a~!wyqH*4_6K&6isxY zjtn%qJb5#A5uRxwjq)02zoB8d=zi_OH_`9ztv4>SiB?AdgFSpbtj1>(=?M$(4<0^d zr1Ov|*dv~T%613Vu=c?rk)6GZ<&Yq9K;v{Dl#9Nn1ePaUS$yx=$@1*c(emUW&-7%Z zm-^tisVmgn|E2Eb-1u#$?Iu${uIG5Qd!H`8DLrNC-w~nv)Ks4G*Xl*ApY>CFy*L0MtusaTFar{{vdxS`TZW7Z&v$rjpaWkkW-w+ZzMl zsYhM;am`+N?>^$WSxmEce!5y-@f3}7-lF8y2z7hH3iB2AHo%^7LExwFUJB8FRO zdOq11S357;6L^knK4(mo_!jXU?mIjVOaY&IcLGQ-C#FXGICCD1ebpK63Oar-}wMlrvLAAaX)u3S}xp93J&0c-Eit zdvQTegy~h3z*tYa%OA>4-v%L}%9B~j^pE^gh5!}+bc6#p9l8Y+L*JC`sAHMZ2Ufh~ zx=_LaBn2VJA;<(K^8f?RukaH{fYIqtkip2p^cBpO7S9Bf0^opQblBjlb6IsuOGsLs zlW;^O4+SFtJ6ilV49Sy2?t)(M0Pq>TK!fxIsS=%AuFwTl4GcRfW`5ARe44?rQJ1iN zIbZVy7k)-%!lJ)bE)8qL@=wEvXpC)rt!uscTE$*jS~|)e(?CBhQX0_1Lc>wN0QuJq z!#Mj9+Z6V9S=46abi%!a*LO@M30z)TydtRiT(X|4kD@PqH>GcvA&8v6lL z`{nqrEW9(iddOCUM^D(r{*X}`!D8XjHze8az(|X=u3!G*311e@pE9arin+(gK*zj- zmTO-5xgCdsv9{tBF_`9@kYO9Jr{!bdGRWx<9(3|#LJy9H3@-J$P61hjH)yGYL#~`U zs&ZXOyQv?io)a*kuYRP*C~IgSJ2-^fKKUZ&?q7q|frT&nV>bIjADhW<=Qb!iRlbi- z8OQCbXXx>S+q+u=*t=IvDP2l)YsNb^EWdsCwh^(gxK8YRJdc26eYjodOEZ+ibghbg>|iC;1(e zxFNv>SJzU}fso?S|LojcB=<4f{KFUJCio98cwhqEP-$FpXaLtnRF0MlCgSn+Iq%Bh zRG$$U9fbi-7z37^+sRo^8GR#Cgw_8b`eJdOoOCE z52zA!NX`Wd>&g?qd_8l)mfsAgZ)mmdZJf$0CGurXWog_{JNj~Q9(c>Z;yRP~kB(3I zGMeHoJTi4aU~%EuTWs<_1k|Z?$n$sH8Ni529i0)pUT~G|+i(A}Jpblv_N%dG!~KEp zfBF#%a7u+(~<3k{XH%DGGYD81CUf<+oqZY`uDHmrhF3!O06lnB% z%m~z%**bFYWgSj|oleL>`p1sGiAj6ifm7AG9ivA}qtlsY(s-r8*Q>e)GJfC-eF1O4 zIX?9zy1WE~MfW$nNAc@#Uvj1L_44Wsqb#1h`}*xU4`}3Z*6-My3(vlE*G}d10rq*s zEloVz{{a_1P98FSKRGEN?=lVD-{rnguX4h#oq>LQz*ZMK5!-_AF1V^A%Vo0jT%DFb zbfVK~1AW4Sx-JMf%2oM(kzHTVcljINNUfC{-t^62MF*1LN#jverxv<3$ju%w3OJ7p z&|5HyzL?Y>^kOZ10MR)`L%W$r8xqW;ESonR3@??feCH_CF0ew>^<({nraqOZNyxW( zs*uC|8d2&R3QBq#7DSYXvSD{?+)!BrNzUlJD z3C%PpO~V+R(i~WntwqEmJtZe_=?Lmau#^dJ)1!)W3dn)+2{O^QE?EW}j}1{BIp9DI zgWX>S$Sh2gpv)dlWlj(@PaJ+p^7S*LE*Z2jnrZma0o0v=t*klnJDj_&{38n};;*`p=y(%>A0wD1cy1Ia#(X75N!yUPPMaenyG7t7!M(|^LR z*u=@xOW(YHvAlZueEH$|i{;OM__yWV+aH(T{^s-Lcfb8L>rel%JZ75o=WWA?zg@m86gs_vsO1DaD?gQo$!r5SqW8EF|%z406_vNco8*3a}i zzBGPkJMxGM@+&RriJW)qk{SW8KAC_3WRd(0(EOiFD~iaZWymj_rWt>vtZ2?r2ev?; zCeP7S>sp-l(eFvP^TvH)Ws?@YRmw1|c!D!zLz4P;uAu*yUV-7qpr|itquO9-l&tkl=(N@W&MHE9 zE`H8x0M=C-l*v}KqED9j)GbI>-wOmZPzyLOsCry+q#UblG^}HqI?^2m$&{94WeQpw zZ_-bS>ey@|6Hn#qII_;i;ABrXry-lPbxk>3Gd0~4H93cK}1S^b>b&I;PjVH_w*xm!PhH_AgBRv>xTtG z0zmp!C*Uy1wS-UuC#cDTb8;9uM8-Sxpbott;FQvRZQG=)J~}QF zu3gY4+@zZ-Q*6AmYgJ!*VL=SIfd#<)6<6&fSTd*w#q_^r#S=f6AYMGLDuJptj?Kcg z+DGmRSia zrA((7KnZc~C`fOJ5opSv#tPrmw@xFMoN28doHW6qMBo>-Q?u4M#96*&Bb0dmJ^jDI zMcslSumivGrTbeb-VUCPf=W=1bz3CXT29N1_*{l*dMQl2QNJK2Q_xV+z)tX~ZzHWF zg{usv^(~=*Y||Rnx;QG3sggV!meXc+EOQj|Gs702i{+*GMqrc~terx&Mr1>zuImXs zC=i3nyBKC#0UaBRQ#K#D+y#MsuJC=x{fi$j??2jEK6=V~7N7r>D{$X2<$Sxme)-e# z{QDo-`^D6TeOiYsyepME{oSbO)J^A?P9s!x#@G1i;=0o_VKm?HFuu?_8X}#YU1URp zimVss?7$tucxVWjWGugws_%hIQSmYGYB6aH)NqiaM~+E(Uxb zi0JkT{Y+0Yz^iBVIt|sJB1K1-%QK#E@qmT)C+~m4bowbTC)ruP`|-{4;^hT2 zcnBcdA!-w?E1_|CfGmWO*I-M#kY`Ogvz6h^0BZwNS8jcA|Cs)%78_+=o>PyjsSba0 zSY^{K#&>ch2~nm@5ryyMPex@oU%ce&GyeFg!Q+LLt*s6?U>#L)sL5pmcrTwi4psOhPRQomq%w#bZ=^|~ zg;@vVsPXkshSKJ#c(0*~TzUO$xAkB#P9%)@c&fK~Oc_;#r@X5@c|0%-rLo4~M?0>s zrQC38U~yS4Ey~5gH!|j#5SKhbvvZT;nic{*&i%@mu;En0oGUcRG^=)SPR7S-nIm70 zI?XhhQKT@f17;Mi2-+BVYUS-Ui{&_GxBP7TA7mZg8YzLR;3_p65mwgb@}P*)>ko!( z{4T&FDzY-Ya1@2Z&Q-cu({NF~1}aVN9|dK+W*cO;L-5{ANdQ6+kk?gS2phP%T8Egf zmGvBX3Bso%fhVlh{Q9%^m;d;?&z5IgY3vnwlo>dV-JanHNE*o{kE|at?b9(0BGN6*^624K0kZ3%pa-uQ!hy=3w?x7>cjQ%KN7HfFSE=hLVu8Ne&s`Y+JF>O%Wk-X2 z^()(?8p5Z^QP)lhz1LS=d+VA;^Zr8vt54>*^D?S5^GD$64_jx{?glaH7s#mV6do}f z{1p_f>J@KoZ$3QB=X|B5pUi3U1|xU+N;y+rJ>Y<-)Y-d5jBremoc$G(^gP zA%lbY3h%*_&LOnsdtUGYAZOyd1%B(;2n3ve0m5CDKhj&vBc$~USb`EvG$MF94$FwA zb69PaL%zge2?xt3g2B zi!{Uo&KWUoOiYQ~xHn4U)tE89yt-T6&96S52F4(+Xfif9EtgnPDll$P8ug&VB&1MAJV;HxI=Y?ZPz_Y?B zqO$fKrp%IL1jI#s^I}3c^30w*>y)M5%Tsr%+hMpt&ddo^Bs-hDXoy5y){##shg=*w zvNgyn{8yPX;DE8A50rH#?XUG~AQ+-xj^a|b#~P@OOnVY+Adnf9k(7a?`l6w1kE>%N zm*wgcD0(uD(Z{28Io1eCx?VI-m6$w(g!kULObLl-N<$pnl?NKv!)O4G{-h5~JPswfmgLvcu@1q z<8h-P9a_G%8;!tTHYdksjS95}vzkG@7W`E{gyaqQ1I3y$AOHf`E5Ia z_M%ArYcnoc;1i5#7;R5S3xm*g>Y9V5TLRMROAUIovq*r8N9)%si&ya!QV`?%n>b1;zwHM(j4o`6A z3j+>-4Q^12PwSR4f6WWeP_Q<+(pQ4jrsNs3oOCCIjm!pKSQ~i&0K6~I+Lv_q$5F=& z#XyUd%`|Rs89_3P_<{uwdE$tK!H|SBH~gUyhJP9JJj>2kD0CCXqb7D?k*49js~dSRO3 z$cV9I&ncAE9Z9MTM?yB*8O6$@#ry%|C`-~HsYyS2ff(F#w8XvAnM(^edO zD#BeGV;LRrWQA?`zDIEK=?;hJ#QP)zZ7|Os?6Cf|#}!9*wY%m*V>WzTw?owh-NM$|A|kklkGVV;EN zt2}i;mYc`XCrAWtTOhA=@Z#_T5AsN!DSqbYZ#FG!sM&6iUvp}C0>Bu~p|nbJ1a#+^ z*Zd+0O;H5~w+f413rc`(K`6hm5$iO-H(m4yY$GMo^^_`|ZvjR6DilWV=_j!;{L7O< zP}ktS8| zztkPmxE)^LddDKX_d#CZ@NQnU?2_|#t;5TVKG<8tBWT?1?MBff8oE=$L-;zTB|Uz8 zxO~7KuY=py%U{{Ux_9%AJz$TPkGO^BkOvZ~`)ly~zRODjpF#Q1IVVo$bZqX^gMTH` zXOI(V`J*wW4}%yDS6ib)r*Asw1VC%)h#Xa-IMs)E#R#sMPO6)!r~O@v(HG>6e?I)^ z1J?1tuT5nv$B~93TjC;b(Tc;kg6@z_Z+z-0P9TwgvYSYt?T`*|3o(l;WrlWKKrT zk*v;-hGs{ZZYg(EJ9^cl1FJ2niGrhc&`uP#%peRthssfpovSv44yq|ohJ`{^M_v7t zVv>$KC(T*b5J%R@))p$n3ZyNp4_^M~BOC&WPYc+{Hra;Nb|fv-w1Fo@r( zq(W%mGPMP!fwhkCeES(R;@EI?q+D5@8g`XVS}A;GVxtcY71ndPKd>aNob^Q^BYn3jLn zvb-|py3rL4BXQv;r%?NGs5Yr#?-nnl;m3yUw4n7p1%uuVtE5iH&bhD7$5h{NW$^0a zHFFv-BDiDSshUR`EBFfvjFiUU9vJ~>BXE&oG{7u~Hw3+dxAS+rhxyHq%Zuk-08`^i!O5!=j{l((8f_ao8OkW1Pj|3&IHjcqwDqA_sICsJ z9YnB=I&1{$F{G>g#|m^R1lJu#h}yw*DxZDUTBK{f{58D<*y+4Cn7@7VhPxPkVl(SE z%U9ogv;5_o@0ag?dd+(kua~#n?Qo8qZwO-D^eqNP!gey(tUp48&V$jYBR{Spzq|C= zVlQWND|^Te*~IGJvF-_FN`FdFd&C8Vhup@cKlD@*eMMbj&zXL!bA4yl!O}&;U-(D= zE_WEDg;ifZGN*lfYrsb2{8ybBF!Yhyxau;^7c}6P1_(5tc^Zfe(2A9Rcp_h)GgBVb zxkvkm^xs0}+P)}5l=)^c?FKL8DTC>Lkj0UfwWPoCA(=*qfbn8mh{(Ls7&ru1DO;YS z09&CA)gWVdtyM})BYE!E`W%BNNnqN_41iuw-{2cniBeeUTPDZg4K;YAiQPyHehA%t zzZuaDj#@he8u`%}y6R>?kOp;KS8qxK4xch}158s!c@{5XUbA8L)yvn*^B=xnzW(OV z%OC#qr{ynS^QfO6pD%CDxqnmY=zNzQ`|ha~k)x@7Y$6vf+V@0R@OkbNDlWVrz#gwN zHV|K4d^e+PBk@kd_+`| zuuZpYM3f0<4=nG3cC5^E%dA9NdLlx>TfNQ=X2GoZXaFJ#9s#Gmyx@fLB#{CHqaM=; z9gkwCTu5sBQ(5WqD3Q77>+CLq55B>|FRf@$3d>gm!Z#_t&{3`AV>;l5Un|(V8Nl2E z;qNItw=ijA{BY;D$Li#wJu4amndmV4D9x#3MfrK9{AAj_et8Lzo_l}VU+Y!*m zdrz5GGS&HsXL6l$WAf`)+z!M091nS->0_R1aeC^z9S^%vSv|QYtSLGo85A6CfCzo4 zF+QFn6U~YA$Q``0DzqPMuRahL&+1gCcl6@zP(7lXk?##Jo!|*uR3MZ&Ds&M<=NFGU zCRM}I*~H1G5u$pe+AN9ar#5aLH4P272T;lpJK=Y}KFle+cL#W1rhW4P>xU1y0sf<% zPnKugV)dWz{x^aaqbNpBm#oMA$j#jU@0WjGetg5t=!kpAwEc)z$kibsvs-E8&grSU z_%9jX@nknT6xY32Z)BS8bklW3Z;?92-gZWp+^*#N27JVi^66Jz;U=7M4%5gu@{S&L zSHrWxB*xVUqhRgPDARNGkbU$_PEt%Doe5BVi!7zO`k7!QoaF|4ZHd1BX2fMyBe$(C z$3|z?mqmZ<$l=4I^|OzTq@qrfq6J5=f&~Vy8in0c z2!_xriRn%TaRn%{y5{RR;#D|~4LKn;L4bVAWM;rPHz>%#bodL4h)DY-dy>|{6RnkBQ(O>%wptk{pd7h5L5b#hPW_OOFMZBEq~%VBu2~wT zJxTq(^d|8sR`YqI24nx1_}#GW(BWRfgax2n6P0}Us-|GUv6wVo=n zibIl_x#vmW`@?;cEst(BhTs~m9(#HD>)%~|^Pm2?HDIl8YF`Y;Yigyj4@$})T;beC%qrJVp3To!{&W#Zsk=Lly z2vqU9JjDExAv)zO&5&9HvIYYUp)D40pw}H<;jb0dl>xb zm-M$x2t1e7H2f*c)? ze~z84N}gTlprStVm{jJDaForS>!mI0il@Ax3VtxxaxRwM4}WK#Fzg@w;l@~>L%)+b zw&VE%1G~ntv<0w|4qO7=dRj>~{Y=FQsrBV!XhEqmQ=+^QS9$#O;Ud8IBd?1&^oIcUPFw(hhCe_v>`-2R(35)#o?z^{bpJBY-$i&B zs3hN0DA!2agFq+6-zGb6@*n7U9R0erf)3~GtH9)6D?*!kO^`2ZUE?`K^AmG47Vdwy z!1F^ve7(9V(C30!xAo=(-Fxg$1>+sQ;STQ!;mqpWT}69Om8WI5kGmxlwADSxRkFAB zo}PZx1Cx5GH}!`A?14lje>Li^=1S-tIuYY)c@juh3`- z-008QOO|QL0^LYoBSObJ0U$6`!Sw**=gc|r|6udIFCL}pF5+FXKI^$fx_DBMziaO5 zOUo_1aOVD9&q97%aBGjS6jb2NW4`oak@i@-YbRE?&F4J8mrr8?NdgG+|(*5TOKR8t&HcKq*;5Athf8GRNhjr{2v-u0ZRLY8tJ zoigQc#LKd{n!IH??<9C>ixt;P*FeL*&jO@6P@_;*#gbsS!#mul$d@avYlX>mFr4GL zIOpY?qDDC#m7~DE30K;pGlMSqIOqvJk)qG(R7K4@gF|pz7O(QkuPo2H!cH;Kp|jiF z!i~fE+sYz@4-Ti$;it1sqX{SjI7nHU&cun)?tJnMC%v2Nokq9y-*xlAj;OY#Hn-=Imc2YT*1u^nC+$R{ zo&$n{Lp-G}_3bvNVf~oOa~?hpjO=^^AiZ^%w|6*}t0z&w8NTj1*i!o4+a4_n?o%(I zIJUKZtfdGA^p6d9$vC@&8z-ZI4qu>~zQY$IdkVny41V&)OZacL=6|Z1ySI;;>^HPD zl)HbHJ}X;JbCYKt^5ye~cBp;X<3`_HUbTDcr-GIV`}-%&i?v!`KqWt08J60zXuZFd zTO7Klu^9FE#p&4VY>S2|c=n9tj!w5GFWs&8mQQS9a6EB=-6?LOKFLyV4PS|OZP!$D zP(gZ%ZZ{IhVZEB3=Hs3yc>L(E^)mn5-RL*<B_w zdYKDC*?90LtNh|c02-&GYlUQnyHi0>uh{X}F<&ZAhw?n?wNi(EF#5M_T;;oJhORg~ zCof}_BFID})R>V!X2V~+~(MAc!q2l5_ zW0y#dv38B9#h+*wd>E~AD%>heR{ZQ%z8*mc85!0YJqQajIpbE#S4q-J6{aN@3rL11 z1-3vQTDSD_={Kzj^YYy$^j`;A@X~*>qs5z_Ews!I3G69=H`M@v*R&@)HVBrQ{Dv6fLLxxlKL&o~2hT?C2Y;yjm=I!b=?h&$X zq})CB7*bWOy7M_Wb5eKpVvkQH)M%8RGknfKyPMnbzRJ#|cQqW3l8aCK(@31yQkfiN z!*YxX4%OQa*^(@77I-hF0AfI$zfs5CT?=g=nt5xtT}w0W+Cxr|stKsEX#cl|SC{9P z-}iLF?^}D8?pqFG(Jluy>|Vg>l0TlilNHJ@=bPz^&qb`XigZlG;L;!zeb>11;w~z= z>-B(T>NXN6_{GS6@PF3cl^lHy(5`ldM-Q2|^rffkVYv z!0KA1mRAKl!R=h@k!`HLAeG~+!o>u5uq&5k;No}!8(5WQxDLLrflvTNKPHsrhzC!s z3QTw=j{h$3JLwWLh`zz31X}8Bfy|L+wT{c-=-0F*$R6%s20%Z^WSPoY#qQa}y%Vwk z_N9r~4VQYQ*^}>je(=}bD)04#Uw+6@t^QWDzj44=rEI5)bSZWFU*!R5C7FWH8Rro> ziK@bg+bLkpq28ZYf2FT-4UeiBo%5dHIrs4cGTEGy`6%--e89HP?hNXgJQzxL6u3ORt*o>s z%WHd@^(@m{I38!i?FJezv7dem9J&d8EGQL3=tDTMMC3zvTwndzep~7NL;Lfe*V0@ z=K>R}w0t`lz3%g3+*e!k_4eJ}<=brkZ3~27WSgIQv(et_?R9Dy;n7{g&C0K?S{J5H zB#(Nvbzb?Yjt?u#eM>#?VDgJlEsY<;$-DhJ$}0%>tI%p` znPG5xqW!YE2zWr{p}WE%0SLA<$HO^lPC6ukH1y60z^UNajpIK!zx{{Q1-E*a>E({ zWOf{j$vhoLQ=m6NqieIx*PhTpz+Ylf^D;GOJ{-VP3PYP_qa)Qy~X$SssuLI z4{y)hogUa4zZCn#-Mq@(Re{#~6O58)y%>(nhNl^!sEkxvdbE>;-l-sMDaxBS@6+Gw zUi$I3-uV5dp?B{O_{Bfh!)~t54F>vCSLmCM8Z?rt;9%(aT(A#Duid<&T{#RF@4Hd6 zJ;kDh4{HE@h6}z@)me>Fs+(`*ImyT0l<{%wk1LhHM@-oS)ODpgH7qkjdY>#01>DD8 z_129Z!E8N0euzM2!O!jebbK`(!d=0hm`|Ad&Ny!3@sbQ|5mLj!N4<%1#s}+}+hO#MrR2q2#MqKlGT(>xKy* z+aLeZ-N(nx+q4(^FTc6`>X+a4wxQo#{^>WrDbP@Cl~vG+-g+!s z7a zb~qBb*mRHdL7l^y0#eE+m`-abL}ktsgBki@gnyW;UYYh7zq`A3YG}AD=m)nE~ayUrx&-PWyWvdK8Jose7Q!K(uQj)3rz}# zzOyp{4)WlqU01Hx^9c~_f?I|pueq76Fji*Y1MmEF-O=SFebon*h7cWL_ti^ON%w*j zXmZxA1B)2>k9niRS-RS;tzTQOYYUfLg@Sw_r;fJ|c8I8}@6FYpo442&%>^$zr0V@O z-LYZ1WfXXM*1E9gPkZxD6VFz8zp&n@$>t|jcs(yY9TlAQRF&7FKT6;ZO(+MBanVuSLKxG}& zUW`!?`A~2+Y}ad{&kZx1>wCAmqTc4C_hkra{hMBx;CTY+=>gVT%^f~Ik7vEKf&)13 zUc{&QIzccYJ1>YVu*G9N$jq8->Avr?8isE^e0TXSeS78FUHjWVdHLq@?a%KoPYYy! zef6Q;DK3{+KmBz1>rV|aljc!;SzYh#P|sg@0dIGB3sl~TX^r28;L!}*Cge9&(aog$ zmTAx_Tj=RrLqNWPpL`@gqQ$(XV}HwtUg`6b)dOBifSkcG`PO$S^`Qy-Cx(v=YoAot zA7*oi^I4;(6o%K;q6MkL0v|;jeVKY%=&L{F?1(e@crrz>TG08aOJL47@bchucEy=+ z%92|+N59k6p~(rp58sr^&8?oKF(&2PQaYm4tGD&3^pXYFD2 z-g;Q>)!47PrvOoYb^!vV7K|4X9W)v+lCtgtzj=G)v&o^rd8H6xwF1$ zW0MX^;GytORm6t^%UP>*l_8$DL7!e?0fmU>P}dte>*R>}Gw}r**(4U91-@pB^p`>+n7K;vE zFbn6?c3NEnm_4@ige^M9pWfJAYr(wL>HFV)-#aS*+vR`#-wUAcSlV4d002M$Nkl3yyV#bcpe{}7tLMgO2(~xDL*lz z$Q=?QJe~|tHhm=1eZl|nqaTy`kC!Js5Bs=AqIRet7S!wyOP`Q#$;EDBbgmBg?|Mjf{`oiSYkr_vpbRn zn&M1B^;DoIk9*rrZ#VL0qercF_lz+;eJWT`i=WM%bfOaM7s1&Icgaw?yFe1*} z1a2OXgAr_xV~5&10Sx|nOAk-FxzJHSGCwD<2|yYXLGbmfdL7YvTJU)OqW3cvn4DYv zUgQpM55Z50WQcP%%_F9iBU>Qp`*G{fzDwWw#Gh<2oetg<;9q~JC)vh_-~Z-+y8JH% z+;{aH-z3A&Z(sLfkXJoZ`TFwjzyHJK-`@P|Z1k)Z`@iZf zRsee%|Bu~l@g3GMsB+4C5p({(JAuP@VCMH6H;{#)1&C964&5ny@Ca%?YoeVdl*299 zEszE1Px=XF9ao2s{zpF<{E21rf>rvAPu>X#fN~TRR5D;m3swRHRIX1TIeF5*C5qy|I=u^la$U4H2(Bu=1^Id;GWw z>+rv*fqC=#Me~6L@)td4TCn`kV^S~D$J1xw>&ApfybML{oA#t9A~phvu8+DxpL&g} zFIDn~hUkUf#P7T%LEkhqf0=v&;M<1#uX~==_5&Zc?;J*G-Dw|J&~`KmGis z2kAc)$a+!kFB?3$VG}McCn(K-HC%MUQI3UWmUp#x@Y7iNcb?wQ7e3e9^GJ%`e_Wn*6u@=^(|rRp`mA2L(IFrv_BsRn(RDa>H<7;-+$vt%`}aIra*bD< z5<;&D4#5!nf|fv4N*8=9&iT(^mg@9>_^kewLdHFLu(et_l0f3IkQ2}IdhRa!5dj;KBK# zGm`Scj=Bb8_Lg8i_$F5ja&(k%f<11fz z(j3#X-nsbXWpCH{+1jnVq9NYkDzCR<$aX3Lr%E5m6M%o14?e7gSIe^=U}au5z&1eb7WGhhv6%D~?fiS1rvwjitp!D{=N)}s1qqWcVz?sjI zfbilQKRBd@0>-C$fcGYe#y>tw?<$+=Yoi>og+Kn@I!YVaL4Bsau&SSuZ{j$BPggA%$iL9phsr-7>#o?qNAWx z5A5ALJ=I=5t0&rWjJMAUVlC|b@V;j~-&@Lnzq@*7^`h(5e(7yRj|+Bgmk`m@4xdcD zOdl<5d0Sv@s_{b=u)gW>5~G;z?L9Pf{H8|d>5EU@t?VvpK`#LcTpxN!{4baP_>VvC zf$?{(ZxdwoGTo3_kABKLfoJlMetL)wPFCmA3IE|cImd7>;;b%YE%`kWAm5_aJM(MZ z(0TauL(i7}sh-%|?(k+KQ7GY#gcB`X9ogEKjTu(_FOB9&q5;MB&u7XkOtT$t{(<_@?x#=xHaH1&~+REEJ@cFmB z#it?4n`dt>cRyF@f?@SX1sbfGG>6w4YxWjvWf2=P!-g=%mw-g~#}v)eG}N(YJBPs_ zI1}7ks2a}cC%xEFbk7mCsIpE#l`R=7^z*BSUZ>VDDo1}ApN|^`-G6B!Ss-Y**`lZ6 zokZl1&Y3f;eCN&3$XahmPvawij(FXlWBJxgS(y1zi>hD;*PAnIDEFzrVp#XNUeX>o zHdHE*(Tz!Ug!iCzvh1#HI@t4s4bLq-P)4)mA?OXg5sh_r`7xH#Qw#!a)y!7wOz3{lzbS(MyBBzx?xm z{+G6HKwwgM_f z^FL}bvw8oho@lG@pMLpallc$Eso87+CcLv}Xm8jnT=5Dg$d*VrM|b#+ukKu6mB!?K z$-Q8^vcd5Ov{WL1;XrE*I2w}BSWbDXkGLpV&`S>I0J{qr;4L65K$NwT<0>U^NtIS| zKZ2O^6txNdosV95;Zy^wX$yAY+k6(qW1_Od1IK_J`@^uJ^c(UC91L<@^*!__?C8sg z7m%7&LzAZN0^MWp+f>J3EVZfmS{}*4M*mMGP)eTcz}7?s|ss zT|Qv7y*pR$8g6|oI=CC@8O-_7*tATf@-azX9fvWDwdA-nX@nQBgp% zX3i{^$}k;UqG8j>j)v-`eRyAwwiUgbyiY%`Uv;+?neb+ zk81w4O%4CnZWKSYF!)WwQ2Xxdjn%Osbv}@w843q2sfb zP&`R){1}pNM1!|%*MEA+hI8QPzysNcr|Qn9`s7r8`R8YFIOSs@xfY4@kS@KlTbw$a z2j%Tf|8P|{udD2V=i4V;f%9o{po@kL3m(^QezuNSY^Cr$v&gXojw&dpSS8e&O9Nav zk}j}up2)0nm9XAa24W=nx8peL+7WGa2`_N!Ht!=JjtrtD!IM0A76gXhxo>5!3S9n8 z#_-W9kVSbXseE1PTD7Bo{FgRU%+&(EUe5tm`0&{&{GOLY&)=zj;b2(s9rh$bb0&{l zs&H2zYqB9nu2SWo`T=?#+XjgKqN~TDvQKxMSb!U^rKO($vW2t~a|Z3I7JhQz0?jz$ zY%b2BvIWxJvCD??%qbbR{8Wwej!2WzN5^-izt3?#CwEKeZ_A)8_(M+jHB1 z@Kf`Q@7@&X=J)L$QIG5G>!+934K<&>@`mktNbQOL`j#|mSU-$Z&*TnbW51t?fOD59stKpA$Zn(VFuVf_`o;HFbJEGtMYZ|4@ z0M3MZDS8nCN{&_!_Ie)ZHOQ=D3Q`P^hEMRhmT!HR%GOOL+RRz?FaUMk7fu@3u&;cp z*3Sz5@_>;oP_QbdNIhv7Mm|B{0@5{Te-p6l)5{Ac8t`a^fT`|d0Sw_&!e|7wCkIRv zE0HsyqoR!8($;fe8FwD>9AS9zq6L?prA7s%QfrZ`-o9-u@VHmg-?f9mmt=ik#kgzd zhleW7(}D-*v~aU}T}y55C%!l=zUfK@t-_MV7R5g65ypeNZpYB!o=mYnZwd&X1n9d* zP{Qx!u6r_|pkP1!`<(V^6W!07tp3=|u8-Hng?c1f==HF};O*G!4b_dH3%V9$^d$DY zAibbO1!O0}SoKpG&gc`~*-m9HXzgu6_zWj`J!-th39nmr;!!1U4=;elgE_11nYFu+ zK?irlJ$#|9p+}V;kaYkpT5S+F7UX)++1jtWCfEhDCk>fDf3{7L1*`RRnrMI1Je1z< zlVtRg+z-uxy?xh^vhU-b3V7U_BZpQO7-J)Q8mq1dapcb7l^@T!UW0&|T*twTXr z@M1d_Ox6@&4$}SR&&}bngS5KlQbzyOj{%f;@`As7vh3Z6Z_FeVp8k{=iqfyv< z0P-J8M;=$7#UAj}SMuJO`?_!O>fMjY|MPmJ`hyEP!;(LQb0&@R6@(*xH4eK=DcQN8 zv?Jodbnc|nbP5RGvtG&c UnAZ3Nj^OAp*t5gLknIa=$tz(V8k^l3skgA=_7I~ zLRrJN$m=vb41DOruYy93|B}(~WCn}RTOd>Ehc-2Oi z6u*^;UZz+8?y&2XA0YY}qX1#Qr;jZQxa-we_pQZx@}l{YdLEBjcxv8eckXg_Tm2%d zReofmEc_ujBrz3}^i^u7+zO$t$PR9oex$NUA2|AGNq0()3}k_3Eu ztM#jwGQT4S_>8AAdr;h>T<@Md&wSQn^tmO zV)(-yzk4TR52j;+--Fqc3-!t#O5gTC3Gy+2{K!5HFYiA*-TMdb-ZXdgwE3&Hk$&hU z9_9-_bSL;-L(bQB@2G)#*BsdUDrrr{>gpQb6hbHK7_j#$_3^_=j=uoT1Nq0+(%F;$ zu91!>vaTKxvFodv2_EhabdV-IlEp43sMEVad-8R+`2?;%Z~p84vz;f{hQ6o_)#wyy zrkk$m8z<5=3~2JQAV!ca=VM(u_Z`f&7_RxocpsWd@*C$d2o@~?MAZYiywY9WWCR}I zvIbK|bD||L!pMaKqLLOUI-NYH((F8RrzbKLr~asThjIl{c4d*J3}p^{Y0**n^NTSt zvNt*^3(~c|hNZ)C<7@azpF+YL;4N4ZYAlBLHk?{MVJJSnz#NKlnAas75bi<)xeL|Ml4Jve3;3gBIlfX|Kw1{dCY_})t> z?&?w0BMGqeK=pKbc;CF47Ot+u!5>LG1B~&oPWQ0idR6#~gYmHQ!w;wRPdm5X)!`F^ zssKC3XI*wZhmXy_kdTiU>b@(0y%Q8HUY!Z>(+;z(?<)KUSt8$KhlYyeoH`JheRddq zc%`}FU`c>i*wM~N@Z;`k9R{7QG<(CLqAK~;No%Y`KmQDO^ODCS*{ZJ3Aw@@9B^ILhn zy%j{0@zRecOzvcKjh4o2{*Fi2>GjOt3UH+?|LQWB^eGKqd@f*pl?ew|+}WN`w;}D3 zOd2}Lo8O!cjbNHN&{~41()lJD;ag1zSZ`@gR#L+4dxc{g(nEX<)Ma>9sd6kWz|t`V zR#LX=s-p|+8Cu9^yfQc>#Dj-i73^aCg%51kopqizheJB&b}U=IJkENB7KT=;gegAM z)9aKd*QevkA0C&)Fixa{HCF1l;5F-(8w{Ys8`v)FiKFM$@bT%(DqPPUe)=f~FKB$} zcDu<{f4bJ|UXOo-$iYkW*2ovFVDP}`3$QAG{ESsf%dzyX`l$@w$gfOkKAmr3xB?!6 z*8MUM^|l_Ey{YaR0_o|Pk9qM@Il5*0LLI&5yjz~F|9{oT^c24FA?=W_9DSU0#}l~X zBs@t*b(2G5BN*G(nam#W)+!4yf#jTD8p?faa{PVsU>+y(B!aRh@SQot+&nzyv&KNo zEbylL2!E(?y0gN+8!m>A+ioY`yu$ABTgxCCTEA=9 zUI1$|hs#5|Puw-RZ<<77uwh~if%z``>I+YL;&jK?KUasgjH9E5ZQWZ91oKQ6={xv& zdpOu&pY%#+D#sdsiF91Pa_UN=>{vqjPFD3NdGfSjF=W*Xw)~})Z@cUG;_go!8%8$w zN2BmXsRMn6W2O$jtj>L$t~V_YXkTfIB=LMW?l}I-2@kxR=$~x{J>2N5@Pw0|pgz*C z<&?crn4cYNY<5!RYHF5A@b!GcIwy3!Zw%p7ptuH?DX!9MJz*J+VEltEja%LV(FtM$ zD=WNZiivm?uxMg1rAs>WoZtcd%qoLn5Sgr68fC+Xx2vJiAC174H|>|7qNMv9UT1?j z$`B}*kn&C|$dy#4t^>@Zqq{O)v8wb9S;#Cc&DMT}hSR3pChwp8q9MrZ_N@B3x79pt zozZ2mt?BRG*2zne4bdab-8#~C2dfavR5@bnfd zSF*KN<|RBo`KDgr=bne#TCg(ha5`To;CK&W%S30Z1+Mi#k4`Ws$W0%5jp)$F^pA9W zvQ2*~rX3dC^;Fq6H&VK4Wi6GR9H?LVTJkQyz3Q35pI^Oi<$8}xHTOkVg3EdMT7W%6 z@)nw{Jf7%3%1Od!qMk#@pyxd~g?ik+J+kB{3 z)stDld9=thSD~T6`!SrK%M(X+RK-n-50jIaik}Gar*bu0F*srznDc;4~(<)_PwmQH+g`Qy$Xr{~cL-{w#i z7`@8VTlDvmlYAS_tD!rV7o6hPBEutHfLX%~UVc2Xudl}V;8EEBmC4ExZg9w)OzIh( zU3Rgu5!RT~u3bOo7^GcgD)kH>8M-tRszsZv!%`gAG^2C|9}L9GWmG5TP6Vl2Z>_>z zN3?RoJub@Mv9gu<%E!vV&AHL9V&fypkURnE;FB=NBskX6R=PSuP6aeXbCjdPTu*7k z`3(+y{Zp0wQLDEfzi4RmvIIjqO_lT#_?6pdcNw;-WGKhc=?tU?zj0`FCl66 zZvJ&z`gn|&sU%%(C_>Mj>n-UK(#r{=kyfEfpc5iBebsQX^Zd`k)vhwJw{X`Gc}}v) z{s(((wZE6=BAeihUdtb>3Db)#AZyrJDU^NVgrB4vs-Rp?ec%V&(kMz^(7WzJL$)M* zV>ss4V8PY(>Q~A1&N^tV#L^BrX(Jz|#|^QcHvjdaN&9aaI)3x%PwiXQR!Xyf@X!LU z(0sT?<@QqFgCl-9hUgUi+3#?8Zr~_9Uh#x5e2^afx;{3M+1XVsLs!ZvG7qX0?L~iy z=Zcem?b%HqV(&*5!d&w<1@|W9n9KX34Ffsz9H64 zgO2-@SEN|J>oiLTCNtLqjF{kwhtr!2WW6Y6s%&stLQJjtXwg-+0@uUyDm6bh5#G)X zI7zT@#V}_5B*@VWCHY4mPvE-ZP|^n!p9P?`Y|4_K804Rl;Dm$DgNv_I9_gWdJv`mqo%x)H?o{hN2^wedt?PJ-=mNy(#;YTg_`J5X3wG0a zOBJe=X`o@2b!-KyVA$|H8Q>0JL%H%kG_>{1ZDcTCz(;eN>HB%}U2S?@@Tv!PZv(>?IP>r5 z>KU02jM~vvH=4|X*vOGBb-sgIL$m96G^CfXG7E&KEL~3)I**4_KPD+8;inC>l$5aF zU`uoAr}L9~U?#uh!3)~#lph+Oi{|H}vK&Q}KW)qaD#(J<2`~#@lvO&Erb!MCg(K*z zjLUt&`I4w~xUs+joNZ+!1=Dvttzc;#M+Tk@bHgNecBkG3AG^A=O; z{HTzOw8g3co6}i!G(UpYym1_K)gO5k!3kOdBx=;xpUlu5ey6a2rOV1VhBo8vUz6Fza%{l$;$G5EnFQB!S=~=<+c^e_xlgg@aOFpa@^VRPuoTSNu*qnweXrYU# zzDvh(FVGE_-Xl6TR=qAj@3 zj<43#ttIN2(@zC14|IPnaGkr(*|w!Er zXd#|DcS=?3k(W?sae0TA{5qHe`w#UBOspu+<&&lasL2I~`X{fS@+T`e`GYOpf3`jP zxLywn7+NgN8K3ItT0O9l^ZxpwktutM>3sT`cFG**2L|j{<>k~WzT)6H@U9#s^uMy4 zN*8Q51>lZ#woj8Z=3WJy8XaLGf>PkScv+h2codDLS( zdK&gke`9SGqvo_L8Pd&xb#dcU`UzbuPk%C_n=VJ6{5c32&QIx-K-cg}4|4lktwedz z{1WYuBu8N=(6!n@3LKwX3g8`)dS>oaK5jq!$H}n$wwkzp+}x9E`gTWcwCSyrRWJ!D zX3oZ=+_%L+z#rZvkJwBVMLyf1jY-(6Y3s=K2FGV@=RJn+eF`GGkz50Bqnmzscm z9RF{_Es)Nal6ibw<=^?NB*28Qu7u|^Jr%K@P72x!W6QKZm^r{F9?otbuDi!Q4)pX_ zCy+HAppp3^xaoJcuZQl z^RzP)VfI>43$Bsv6GlFc3Sa3=J)qCci6QSideia(#KCF*`=^GHt%+L@+med#=R4eG zyvkX|Xu?tSrUI0A&9haa79axZVyW?c4Q^mlF5fi*<%N?P&$WEX z=yaz0G44*HVdx}ZPL3)}iyEKyFz_#b-5k!(xzKBq=09(d z<^nF+=}uX^({-|K0yiG9Ib=WtG4q0egY`Flfha3EyG*oxuHu!H%N2C?@UxZcBb>8_ zPEUX;^-kWk^^GzDf`y%}(=7NULzUN7H;>z<*#c1eRN47LdxEGr(j5hx8_!pn*yNS% za3)9j5!3S&&^*H_V11}twrzP5=3RiTS_mCqfRB{=S}C-kx`uF~7EJ2Zcrom1O>k@9 z!jGN#l#+xj?>ya5naZ9ys4)~oLy>G`GejFZ+G^)fk6+zA{$<0*0@#z@?^wPdYHi=5 zNrCQ5J+bxfDqlpLm!2L1Zjp@RzLqbzNwaz6tHIe%FEXg<8&6Kwo9lG)2qOMwOnReX zL2&>Glu-V)zDb_$!Dmas!d6SF_Hth$P##;`w}L4%g7Ri|r?k&ZMJ=B!|rCiRrZ`##Gf^QnuF#2b@zZT#`GU75N9JM;lgI=UveS z3h!uyD9^i1>ccy}=9M`R9)pLMPF;Rgc8E^PlVQp{Mt7_thWjW)MY}2-o7YMlx`WU$ z!&1)(OzmBdPz7=b->;MPS$E4d9G5<%opaW7m*d_)$$6^>HubyjZdNrSr)Y6&E4D8^ z;C*KohFWC3sHle992w^eC;2%VT^SB=XnNX58`!5j`oOcSKn@=I=$un&U8|dYRA#vS zl|Lm`enTg7QFb0(U!iqOl`|J*ZX^BICo71>eHGV((E{4DC-p3<)acKSo#0=>*9qtH z(m+9}r4Zq>l@8s|qW3#C)mHH8Ti$q$volww%W-gfTuIMVgFjjU3SyhDsu%R8;8)e` zI+>>TLb7~+9VxPSn@PJV5&wH!XTgDAgi{%O*|4zSb>H4#_qOx7&v0!G{ir!G`;YN` z(-wP}IGf$KyF*%u<8YIiuC9-m#mRE?_84kAkKiX9Ci*>%xaSmW(8;vt=kXK@e0G?Q z_<{1iyHh&*A0H?*9p0yWa#RMg@bj6wf|gASpLFN(2++Z>s`!5-8u$wte^2`eiI%1gQ>_j^VzT}dc? zfejoDT+{gX#O+#Hg!sx_^%Prwgs-TUc4Uc@KvV1`nd8OOV5A4H zLZH;#nBa3JODk3$hc_X-sRx0K6GctdwA}feVRUhV_7zv>z)95UXmB`h0SfWCu86>~ zYDluA*SnV$~MGgZki7tr^oN&{zrIaF?!c zeXKqjxj{+2&MfD7Xj>iciTvC|zB^Wr3tUfn&hGh}rO(y(kB)$U z(v~k9u2+uK<0=QYm9mL6%P#0b!}O%*0MC+w9*=5X%>HI2L(_$sGMOvu{6>Rba-W=NUcCr{VK_==sZ!q|p~Y zt7i9mdEeZXcTU>ya6QO+Ry!}SSp(*!8V~KaZ`V|l_#eBo*zRC+Cw#aWB5-cLX6~S0 z7+=y7gx2&J^~U!|PbqvG6gZ2MKe`*8FL{UPy_+Wu*`GAO$Auns8?+LWCvx4n4sXXV z!I~?y>qhrOr(*IM*cn; zuL?p_{DoOhr|D8%!MqAH!Y_?%>i> z8XsG`_EEPB&X8*}AGW0A;I<6M4jQ}pRG~H4o>9#|LKx-d#UuLM+Yj{MRB43~g(_Q5A`_7}LWIIg4z=$ua2{tUl6y-Ihpr{j?Jt0!9xS^PzF z69O|tiEgc{m?OpBVbfe z`dBuca0fTKT@qpy<4SADhA=#%6aT@f%&VZ#hZlFqts-CZ2UPlSToD;gqOX9e&Ugnz zVF6GU8T|I?dO>kDj@8gQJP0vg>aniZ2u;~S)b_7xMfbyZyvSl>oU0XdyM4#D(j~f=!#^=PY!*Kt8u2kyOw8IiZLGh8CT;Izv)X_ zcyg0*Cv1-|2Ce*ohwJdv;R;BOU0wYLj=1Y<{z=edO4l%2tZClo+&Mf*=3Aj)|2gu;=g3Y=_I z;~1ppFh3Ra^mMl*MKG>6c=Y4E3c6q=$khuC&elDJo&r(+@v5@KOJ5?$m?Kr->avFp zMXij>6yihib8<+Xx;`Y9(Dizr4BOv+fx=^WbWH<$Se223beIYk;9H+mL;IzzV)SF~ zn>Tv$tB1?W9#47qx9&#%@KY1qIa~vSy8@U8yBA0}Xg#y48}!+zvt%`V7YrHMXLKr! zJN)6^Ifgc@iOGE| z^l?p^J*p~{Pojgv30PO9!#kT1Jp6`>%&EZ2vg@mwzFs?+_2#bmfdD(WPTFC= zbLsnWo}mZ_4;sts&~a{%AcwDDqg`@>cjV=YOSqo9V3y&QxeuTNyB-UJb`8>>4;;>J zI1djnmA0>3U)HPCtlo8*(_!g*?yUsFDFLZ%4Vo8j0{l1ahWg{f<<*~CV)459je29d zMZYp}(a>PKlGZ|BtH<;>oimCSIm$*Peb|Pu zhKEf(G)`;L15BZ)yzU4x;UT5rqx7*J()NJdpmOs<`IrqGpWVN{Ja4<47mwRAx*+z@ zTi73Yt5tHZ9TA07rcU&%LlNPb4@V4cD%{8Mb}oS*r4yVipwOS>ekOk>$Kvc*$_#kQ zt!`a$saJw0zuN8`S3Q9NG7tR91l<07`bDd9TShTwH+jz=1sqTm?K`b^$s+}#GFAB9 z-GMx(JM#~jY6am57LKm3N#`iI-2z=@!&LfF9Na69hY2_iPdTU3!+DgXtaBOtPYG0h z%cr z^{#h?;nH-o$3psaJW`#L)Ab+N5~c@iE$ByUoWt7*r(CTJfbFLr0MW=kyVR=U>&grI z=0z;*Mfva{1Zf!A-_bDV=;q+eL5H8wD}jmNf*y`VmBSd~3oiIx9GP89oyi9Sb1&+dp%+sIj zT7cneo4hW4^P)TA1>fOF2eCfh+>o*P<9z|uM89QipPO>nbE4`;0Rav}VZ9=~MVdvM zd?(merZl}xJ)OA5Tn*6AEm-}rr4p^SfA*KlHxGX)ko~#Id;5O1A6-4kRq4S(riYH% zPIm%-^&1<~`9wi()wTqBzgc|XAUe!6NENrg;UNI97h$PC=-GC8bWi-1Pc(ze$=+|0$)$zOm229 z=bM2Wh2v(G0Fd0@jmfY3fB|G_7_^-hUGJ-iK&>Trw%%+IrSd>DBqpVRW zdz0i~C^K!DeBZ6@TUo{p?QmVwHkp4dBguzi8Dzl?+0Mf@Yferwux_oZVU%b2+zEU9 zn=hA_|I#qByOD4H^7iuSZS>k*@UFXRy%J;rj553y%?_{zkGG(}7+ds;@{N(sS!DBl z$i>1cvDB;H*5esJahX08ot+bO7N}SC)n;>~VGP;%G@?0pU6M5z!Rb5*Oz!BS7q|cFs9bLHY7~aG9v70QoPiFz|QpU!9*m%KG;pA6Q-3-76obs&d zHxw6r3>3FvWRG`!_BQPr7|Tf3Aj1_g51~WmhK7I=k~4ISj}p&ym^9jZKtC*}5-0z% z$(at#BT%$npk9)mPaUpxgwoN+c*4&|buIq4>14g74I7i=+-&MtN8@Ji+;crlL)kFS z#U4key<;!m7Qnv!QUGh$iD#D|nz;WdpYqT<+2g0E>F(dupImXZYi_f&!If^9N$#sY zm+8DDdB^_OGT_)1j()kks|CICsEmjBu$>yi#%p%NrOfCrU%BY>)ri4~CZE}PN4{1M zOu@c-Ai=s+hM@$Q1vNoPTF(2mA_Qn?IQ%MtvaUCT>7cWhTgqDOnz)59FYvd>`!GWarO`4G7r0n-)P4+fqXp0SEl1#-EHQEC&eQ#y>FeP|kLvu>r_0N~_L7Z1^gfarn&W z$Jdo-bWVZ84$GmnU>T42W~1GR}gkB$74Zh z9i+)|Ca7hPj?^}IMTmv*7S6>z4Ulmz;mSkz-NEteSwf4Gks=X8OkobiTM9DX^uA^bBWm_vPcj=Ce!fylXdx7QFabP^(ywZJp5ddOjHRDoPqXy?|gx z;A)hLL%45&%hHIQ!|(GIfAF1~qds_WTK2Rf5Bn!ix)0?kJ_$I0)BXmFj57Y0(aB(r zqfQ;e?X0{$lrq@h*WI1+PX6*z+2+NR3s&0rbHy2zpXH?UR_=WLtBkw`DJ#3MMm}@F zSI*rEH(c=YkEh>#xxDz@-Q^bmc0h^0e|~y-RqOmN<9~ePmU>i*^k+?t3I&N^)M1#& zmtF`GPe=C3{QvZ*RoNSif<5yU!3T5eeeN7D=6)k#T8q>)ezes&lfGJMF!_LabNTKzc^<)hWgLnF{W!>RDK(8=Td5cj=B5qPgS zhwUGt{E?>smI6=tbWqgsND~wf?U%pxiVdNmu=Y;i*fX!g34@A$<&t@0%wbDD$`uce z);!tJMTp%Y1g8O~IRWrdLpjaG{eni1Y~>H`K-V{q|KJj${y8p!t$bz7T@~l|MbtX2 zdiama%kDtFFqz*?0!u0cBP1O+7bZ$fXLxlQpKybV z_leN;*j%}R$9ZyzoR!fF6U8(X%6EG3z`Ij;oY94>bkOlwSZWs|6bL9fTa-GQR5Y>9 zr@ZyrhsO^)sq7hxlg-fzK7v=UDOfrT9St48&NDl|!S7hUi4gF^2RA&xt^sdl4~>=G zPq@Pm25leqLc-^yA6~}Wuu?*qIRrH*55t+OJJ+bItnkdm=5%P3udEW~uRBWP)6zL! z{Vj+Le&b1zEW7QTujPgVCi)q3Cy1Q@j3->3&-hD!XYpudYSkaL_qrk61%{ko$?q`&ij>uuos$fP{F+ly)bP>?*c|11I7KHIq>8Fjhg z^S6S|uKePwe;CKlWCK>p70u3FEI2C*ItJw9BjY$ba4rK#B;n?odeupSO?4|M2)Mp3 zsx@>*Hvm1E6YS$*xcE}Il7BwiA@9vR&?usB=D< z?)WFXp9HX`4peeOR~1~q^bNdgQLO(2c7wt7qk0J{Oa+a3Sxh% z_hrxfUg;OyeK&(1%)8CjXbM8fKb%L`OAE(YSuZ0VZuy;%Z`YWd4#vF_!NR%ZqqE9x zD{A)TlW1M_UeS10wi8H$J#{s?mPaqr!`G*hBRpT}T;8UPN-lt9IF_5EF-+$gNx17p zokxpktUSZR6u+)Bit(-N%2(hPymp*%%8Tm-z0i)dYg)K+CvTOkw82hGCzrw3${2G} zW+Z8;vm@3562+uED2KNr)!={H{s+Y=Roa5r;LVu;tP=O9+&l>0DanzOgl`0t?;6}_ zI}Z`#1-bIKV|vufTuVk~!|6GEP(4<&36K`M-uLLrlW(6kq2JcUKlF0c|7dY*&m4YU zFAeqJ&4ol?aNSaX6eE6LRfX05^>m!j4!(2z=;cO7|0Th5`0gTPCm>f&Pb3-6TaJ=t z^2cxgL+)htCu!-zbuvwtyUH1>tnB4yI`JDnUxx=*{$h{1`UK1tP7`E9I=mN6D^;TM zGTBzT6m;?489afZHL*{Tl`99Nl1>4+*zsH{tUTO&O8}wixGIpGx3qVR)&d1y)hs?; znWNJyA2&+jBg3J0bo5ExK#*RPM>BXYN%`E$`t4S-;cB$huWYideOeq$zN62B%Mew- zBgR>y)_Sq-Dn2x1^j4v#MW&be(=YD-c=_eyKVE+EV9)!X>wOhq-N`lu$NZHakNxOH z?LqcZ>2m48jZ3^<6K7WHnG8pfs}FcwxvQNakljJG(#tm#R-V5hGMvMWleEE?!B$@e zr~DY{xAfx+(JB2lMZgb~2G+fg{<>PCV^{`Qi4%NE@Gq#jQ|YTRa?~h$?8qdg7eVY1 zqZ8s!u)~o`n>8$$#;IVn1(~&YCpd-+O}K8y;Xh7zGVv6@@B!v{xF@WhQAcx2N8wt+ z@@O{Wk^DGaKmaE%++oNov;zkbWp+wa74^9pY3K&YnUq5le6U*Z{ye94=pTMK+cqAc z9|~T%5RYc9a(r(xIiS0qn11@p-Z}Vvb7H?v(LX#bptJ=|%PT&${j@UP9@RDpt6XS% zWfd#36}@`Y$Exvd&x1ETzRisY4)Id{X?T*hB84@Dk1TjiNVwRLj>!~6zvsg?=pah9 zw>xfhA#ELEso|DTH1N|mK4DmsFoBXk&zzD-sV8HXBPw$G}#%;P;h0kV>yaFzvrs&rh`u%Ug27Qt<>>>-dQfG&Yq4o>y6iEs9)q%+qM0f%Mm^hY!@96q4IOTM=kp|s zVP;RIM(6XVcU4{wWw%B2U2|SfUR{3uYaAY&-k06wK4M0wCq9OibN+ZF78__dh4&h{54&A}844 zl@U@8$*;##nWUYb(QU}LKtoMh_js8R)s8IW`M{&Xsk-xcleUTphO(&`xb(jB#OpEo zw;rG%F@E5#f~n-6nslA~^cek5?VhklhYIwQxytbQp`KW8EqeNWYr}r`^z!)U7d`mg z>grlp+t~;w9tJmOX0fB*5;|*O%AKAfi8u2{^zhIlGgiOjeiganWXMi;?u4admHX@& zg>N#_HAT}^=j_rY6)Qdc`;~qSROZP?XP*IfweBaKvNzFlaPu~78TwChYDJt75cE+7cIpnriZ@S@2HPT6l&WBjtuL=P0d z%9Y|DBL2UN(vNXUTlVM=?}4Dc1+35}-%6b_(-|*7h)z!_dW1~&W;7{!EmH}M?z4s? z0XYTTox6mclz)0=&M6*j=fRF1rdJ8Nz8(k1$kACaxH(_Nfb$2SyXcxD-{?!@3>?>I zl|Goif6AXCI!0qH0vTcqeagIn&*@7)!_rZ^!bLxXgIzw+D%ICS?iu3#10FmLD`o!! z{Towwtpy|SCQCcs_!_V4r7FqLIa$MiyB0CN_&z6o^Q@g4nhR-|dEZ`H_bsh(EB%pR z)bb0CfmcQ8oBjm`fy?$c=C~%eYne0l2?XE`X^)L@QYGuxXDxi^{wRC4gr9SEaiFFp zY6*mp9hJReVME&RKgN_>!C4*gD}5#7n^6yf;gB{CmUd`vfn%xa2fiS5x^u@qXk5|k zIEd`7hqU}k$gAj*`2YYw07*naR6q1P@av_i6w=xMzz!arK8BCpRIP5DM!j<;E5P&l zQ^B)ea;EKYg{*UaE&x~vs~2^G8$8nUg(BhUz2{T&&nyxAdVd8s`t%C4?n`eFq38Kg zS zOToLe%4qFZMv=~?FW@PE)xd(VN&gy{N?3)Yk!#+#si|1Cp@3*` z+0(*b1Cid>@=7Pm@HWi5`56fPwNA+kzq~7b zFeZW@J!#vMU-YW_oXs7`_wPPmo_jvTYsI{sTBU0ozK5du;>govr-D^2>6xHMvr1B< z;EmP-lbt_&Hrb3G@?FQxv3&_Q9j>y09cH?lai=Rd=8X84&xsG}UAhpIH8`YVQnc6& zKq-~)NH1vn?H~t)v+=YuKL>8S(ckjtSUX=u`}<|thdyK+9v%2jZ%+k1z#8SFxzGf6 zsNkC}-HVY&c2ps*Nw7Y`W-sm^lQ>g*SVxu*Q|a#sCc(+0O%6kh?5XNrwIm;6vt=hIKeuXCyN zFvM4zgPZ+Bf)4%6e<~#JFce-M=El*KS59(Ige%WDM_ic>J+I<4#R)zX&+7yn-;4#> zX)SbmV~;HbfcHYgs&% z$2q2~2~eIOaI{*PU<54ya5(rvF+;$6_($bLd1ZKnR?>MHqd!ZnJpAbH6O4)4Gwduo zzP7GLzOu^$d*HRuqu+X;=_LK!8VV!P-HM)x__8eE%*)Jbb1?Qn3s2)Jy{<=5%7zz{nRfLI zT;LJgIZh|1SD_^RUnQHY0fC#MkCVkkNzQ}JQDm)*T6Z%DmCPfffU&M)eLe*C=zk|y z`p0g=M}}~p;aD*A-(lW!<(;dn&O(8Qbae2SkxwJN^5a3-{^C!-;diDT4AV-^o$zP^ z2{OW-vYQs^`g8bJu}AwV$bcw3jh4PZMocjAB8?ABU_;RK^2l&-G)qU14f4yQKNa2R zueq=0y>30yC--kJ-!|v z5_+h2CMmwWx_oI5`n#9yB=EadfcKEwqk3RJynlW9@YjYDZGPwul-`!v-BeTg8&_SF zXYJJ*qd4;DlG^;JeXgdbKuYcu-Q>@xlUHIonwSO?r|WRkI^EKcKk{DHaj192*3ceZ zbXpy_rmW{#If$1rUp?pPXTHGBu&S`}n)D%Hi@?|es!_Q8;uB8gfF1fLfR{qwE0jeN z+bhBNFgfW@U)dK5MDCf(78{%Nv1o7%kdPy(M>+eA}KUu%$x-_m!iwUsm|s z@LIXbqBGX$o?I0!*PN0Ct@X3V+w3x*?J~dc1i+_yREDUZZG_qE*|U7*yB-tz%{M<> ze)Hn5P2m4{dDb1r^O#V$#m}1>Du*pLpFX=>gDz-bVZ+Og;J)T{Ivi}89^?Hii8wf} z)nPQIYqap!@#y$UE5OyIXw!c`Q!Q%XZ}xY6l!F@k58-?=(kt5mhR(yZ>|=jJ-+E77 zT!WO})w;adagg!}uT;3tr2WA*6u0#97T8n-;Nkmvtmy7M-qJG6f@5XNT5yD%qwDRR zVT+u~`N^Q=IGessv@S1@VPG-5aSTA{gWYMr3}fD@f2n6kan6-+2MM!ldGzAO7vS^V z^JwEC=&!iT8@(G`cy}E=a;UtO(HY)K>V^&mR!2U@4p%Mz2_(U1gD;<7)eC#uo4mbc zrw#o+^+0b^A>Z~A)py~0`%@G6z1wd+yX$R#f)0m@6%N}*Px|ohP1lsvyqGm-h9}dF zgJ3s)owI+zY%-?H;I5jS9u=a?0MEB_yOfT8f5;4|>s`QoGUK=N0Ir+`cD3hM`e5S+ z0)ec`L+9_XNvRM{c7M>k{0NRL^t#!PtG3iq?>K^7o|yA<>90C?4H1-vWt~4IxX9AzZPq zh4R?m(utm*&GKFR+>Pkhm+zjxzx=1~e!BeUZ=1M(`R7*L|7}}EKV3l17FXU6|2KVh z#H%Q?Z|4y}YxOBx-4UZhEoSt_H zaO0A_&>d&T{_4m4W`)2G-GLjjgBK3}XC?lX$_(XPFjznfd6j}wWvlar@p@-b+6i`o zK(M#qrO3)l2ajEe{qMlg1XYG&4>Mp@tdrgWtuD*$;I|KosV@J|84} zblHzS94W0t2}lL+Z;54`-~|AlgWJ`)O&;D=@{>Q9F?nFuqzy+wD;`!w7{nacnng}1 zpk3)r8J*LCVd3udrjzbAniFe@Yq0I5->r6!y!`lwyUY8(^}Jl$8{1w5{b?yYi4yOX zqrW}^B$t?`lJ&?R4Ao4>=oa*jGFE2L1rl*bxlsrwM_Uhg=Q0l7BY#j=r#%iyw!_(M z<-G5~b>%~_!q7f~`EtkONxS8hKAM;o{JW(8tBgv`Uf1%svXfP&V)@?WxeBGx@SH@o zcLCx?l)mVO-|G3;GYks9D%7e$c|jcC#+PfnWY#UOxHilyS2}!rPh7Fz8~X?X?qs58 zfBpOB3#>ugR6ukNRZnGl;Jokrr-!h+mmIg1V5+Jhz^|+(GQ%MNhu+1d6>5t8#cJTxKCQ= zUVeN+C$-2kX;+g75ajeN7&u8ji7w_66WlZ&ku| zSr#gbjK%1Ku_MnzWV%;;cBgBn1DsCul!pX-GUI6fm5GC{U6ux2Y1fjKJ%Mwu%3Wz3 zh~o&|)~Yl`=6ovnCgn5GJ{W?eYvoVya_%Cv00ZmaHB?c%%~2fG2hQ+vJfa&N@P`qs zGkV-DuY6gMT`?lGt|i>p)H%xy#Bp@})>8A@0M}(59+wPr;D%)6$rrrjA;$ZFWA!TX zIb8lunFBk3a0>wEMHxsPSTuduA8+SR3R%)h+0zpOOc309bMTq>>mMz)-2>dpz=F9N z59xbaVO^@u1^+RZbqjdw-IuUZ*Wu)Pbb|}9-cKb?ea`NDn64}gJ-d0-FqydPWg%zp z5f6X$tZdtofAjLw<+tB|y!^}e?=Ju4yT4t2{j|OAn{s$;_f@wk+5Y`mclhPD(?n(0 znBXzK*2@f3m+(>R1m}yJ@057^9v$3lcpiqQ$$x+dboNhgN-f8U08?6O>R6f5rk@Hg z6wNMQOO$piKY-x^uN+gJ{Hy*$xr|crvS)caZbw#B=A;T%8i2s0XSPoXb2P}2>0ds_ zxiL9gmv)W;$QKa9eN!SDSQc1@FZ>G%XyRe0JFl$rC9NzT2n1lx;B-89)l?}LrF`K@lBANuXv{yZmpcvb8C?DFu7Czo%3)k{HoA;@2QgU{<;@PUu_ zt@qhnMdx!+rDqg#VpW#}l1>J^lW8?*y)oA(r>x_69i=d&yjYes4(Vu)7`|f%cn^+} z()$%K{hyXpl;|IC5mtG}Nj%Z>8og+&tny$H^FhBTqkdhpO8h$jFf`_G;CfBUSdOLR+r~9~sOSTHvs`(HQc#?R2jA>+D2_^CmrC1>2Y9z$y?3AgkCkIn=}mRypI0%#5LFueWm3rT4xyrkn+bE$Z=3XMIh+o!Lur$@-4NmBIYpeXV=}|%>3NF z;3KfDI}luoe&iMPvsK5TZ7!_6%^$vN&gNfUU7oynae4N<;YGpi?H{{y^{3M7qJOSZ z?xhTyUuwu<=T||xujeZ5@Y?F3zP*q&{9C%nxXHLJev)EAH5QTG?@n0cq$qVX(%F>U zk(%W9&xBc_pX88{mEdPvScYfzQfNYEBB3G;BEiqD>SLFa;1`9X@4p*&w1KKci5$QHhUJPV1}y@f}a%%+#MryUG^76Rc?G(*&)2JNWtj{}5^8Dm2=VEqZ9E=xs$_FtW#Q+NJgGQ`i_3o z{doCrzxeCrKR^GWhqd2ap1jMC1dMb}3qo$30A`v4FAsOzMi%A|}%gpT@5Ipez8$%(1)S`HdPjfe740Y4rF z$7@uQ9J5}OHx7NaI{>Tq99wH7%5|j7kOoH&%;PhX@*fIfFMhf_`dxd3J^%jl-8bJ~ z9u*H?w@pfS)oXn#*P}-dUtV@Qoxy&LW{)TF?j9PpAhO4Y+i%Ki+2}QQjJ54zQIBs8 zNU(aQhGnzERT`q==r$ZD)@+}xHgB{*4utgC&+m*`?*R_%^s_s$#O`lHTdECspvg}q z!1;*1)az0 z#gk-x{=7QaH$8mL9`E12YVNZ~g6@90{I_4dx%{91_4Vcd_%E+|r{jOLP_@SFW46@} zCc>&0T}Dt4eB9EVJ&}P?|JOhyFm63q*VZoPG-cz{b%5#hJpWi4feY~S2jzp`(viWX zE-FhOa62~eoi=Vgt67Yg^D#^u1+RFQyNaHA}E%W}AJm;i^<1I%gq3P>w_4NB8sXq8!jLkx$T)9H{=_9zbHj~4v>Ykts5 zISYPQTH{jkRZy@LFdY!~s&q)c1}{!gd3e6&cdpV5cNRH!B2WJ2S7-yT5ds5qgrd{8 ztWK|=a{@xf@Q)sts{vL33-Qqh%<0`}g{w?@NL9d7UiZDgg&ZEN4NFJ4a|s>wlq^#1 z>PrF7<0N+lq02YP@H~8v3uwKM@!5x8UVhaU)b|fBEv6^3AZTqPR2lWqCwoBWYIKNDy!bXrO2xAOGb zHKz^Y=wCwmq_X@zR<>9|tG6kz(dvXL`3cvCr-Szxi zC1i8u9f3ioj6eQa-mSMXyRm0*^P38g!JW;I1w{*C_h?rs?iAm*dEtYlEhglH8*Z_Oi4O#x`w@xSOM}*t5MO$laYE1efCIKcO}<&;KPZ(P)wnv!B<$S z$&;U)OpWT`(vufpbQE$13Vxcp30Ap6PhGJ@eOJB>RWwqV!t8Iv4;#iX5c`+)rLfmc z7V!$H`aSZu@~ct<1IDRQ!AJ%e<#2zR!HQL@@FsE3V!r~t#$HHtO5}BP7h~#%5meJW zcI$6E@7!Vi)mv@`K7P{+WeYlt{crw8@6DB>QvzR`Gzn4#D;k$;D|6)%d^EszFbT)| z2~Y2BIKFbrbrD8${EhzQNnNc^EY6wo?nUSFYV-Z4=OdyUYmDZC%} z5k|ipgoB@7x!CzJFuEAu!v^(PN}Y;6xP^kvr|C$>2sURd72NUSTyHwRzvkI+w>(+m z39mqV`f+>97KuN-`DOdxZ$EDT`n%ih?K5uLcD*XcBiI=*@>9GPd^SIkM^gN9rJhQf zPg=j?+Yz1%7MW!5_2OD8?7<4iWjm4Zp-g5I9U3Q-eAM925pD z%5a+En~-o=4eNuyUJnr2^S?Y2@{mx%SYRH1(ye}J2alA%cpnNT1+00&HclEx6UdQe zqou(aJV8jocg|fx}T_g3Bk={Y$5eXlJ)Rc--^z?T5BooIl~_k$WD;VlT@U zi^C^4rZG4QIByXcIh=m+eeg{P3NA`Pn{4K zS-kbDg?r(lcfjsXPM<^f8EaU)k?NMt=$!2_7k}H{JpZu$`TO6tzkL6B`!~LS;^i-w zF66W3rT!aK$rFDg@5tspS^ZJ=^qD$fW!Inj7&`b+t_*(jt&U|=QJ)Kd4Aa63D^B1= zpfjZ03duK*?@AQt<e@i4p-Mrrq4!gn&Mn9)baV+U|zzjY==eM4dh3VZ`CSWdQ8 zU?2*adQbrt3YZ;+gt+{Sjr@h8aQ^79)Y}7J9c!>)t!OXjC@BtKwJ$IY{plbnvlRi8 ztvHz8>C0!G&raXM0swZTo>%j#O0~J73>wf!kK4N*Tz>+7_!`PZHkFz2t<4AdeeS;Z zH~LhGBhRdVdUd;f|LVi`mmfZFfB7TRM@C_Pc+P9l@S8Jm`fUDT96lhRK3vZnJ*gks zf;uI_wIxF)v|;i)WnHwzqOHQV@;tXbFy$+d@N=@tvQEm17G(vdZCWfXfd- z*7|}+(P~JLTFvt8#Ze9K0W$KQ2gFfs3VIx=?Wq;bQe8O)si|k7b%G*A5YwSN*CH!D zY8GbT=4h@~QbDVXL%1BJ)%pxh^EZrfZcmY6tOFe1c;?vJ2;~z5zt9ILxU_3h9E1e7 zQe+^6cX8Kepwl9-yUHLq8Pxnrz;!Ws@#2Xz;Ze>^JNfgX3$qeNVC3+Je8<#)dt zn-iOX81=qis*~wQy(NtR3!gHQI|NLIb4IP4s1h@EfW@o8JG|u7M_}TKJ~T{I=6C-U zat}&hY(g=(cYOaJ{Xq~5;>qO%7I1xxgVIep^4*to%~VjZmPq7Vfc_cPQm<3#dV!ga z1yH89FY8^}!@AoKP zw=Zz4;Hsa+fCaKEzoo5B(uMgFreO?lfy}ab2U@u&TGlaXMqq;zboIaT z6h@C`j{eQw+pZ;o#7hb*hpgeVf?ep zfG!Jrgm^)$ZiqDJV?3x;PKD}>5D^z|@J%F>|m_Q4&@$MC8do24SlOvYe*@#jlg=)gocgRw9@2rrnw>efvLTE=_o;Km4}+_0Mheh$d79b2s3kQj>6hy?67qWoad2NS~xsU*%(4TNULoB zcAWBG*V0kk%BkVx+xmRR6y=*$Ui1w*xQRH;M|WSQhX!BD)u#&^eiYmTxSirvN&cM9 z>YaQXx!qs0my2Eb_n%*GhYuIL-TQ9)^zQTa`6G+NEDT>heLlOgd6I>XIXW|Fu^x8- z$B_-N$?OmUdbESMKV!$gj?z~`+9rOqfc!1qNUD=`WJ#=>x#tK?9n~X_>Twjrj*fgW zB_qfEqZ<0ig8O$Gf#89;dUe3|BQwG^JlEFuz&299{kP$8dHXQ8w4GV&VS(Qm;AV~_2}A-(z?0K z3lFfPDzgnGbMYl+#wL{cQT3CwOdWe}4quEnxt{3{l$33`C5=N?nnNF~Yf%+V3nPzY zSXUVRRP}K-^r~pVl&{kQ$)2-#`A7O|bhN`%4+;_C1TqCJ6DzZ#gw6{K5_Ul9$L#@6 zc;mY5A{ngHG>^pt2jl;e#^ybN#$HGcCrF6{{Lz;N^};KFQ$~mX)FYZ6+NA20NaV%hq9Jz$=6AFBlefN;*Nf7pGnD^a1&?UF!MLv)&{V58(D4{=l zAd&xiWm`sN9Lq$zW{uHEs5@c#0j>RtVnzP6eUG!_Au!hHT``UME;j}+p`Yp}t{p-ej-8}bqRoiQa;cgonChtR8? zg*J9$_Y>Ygb$P`bsIIQJfBIjC?Jxgyv;E7TuD3sOd(i6_AKB{1)kZp;Gp3i`d+EBU zzSG86onsF+i4IMV&~9U@yrLU7{*p{5@s8cKfv%}r3$yD?U8*~E-99{kyjW2?3GcKw z@hQK$%XK)Hh^Wj+|DvI8-N-6WKhlWg=uO>xT^uGd901LrVbM2F#-+h0Uj&!zMp*q* z=0u&!poPk;XUUprr0RaTYEVZQ0?h8+uv%e89bo9a+~+kAgV$j6^~lP4(|C8B z;t|#W>(M|ocx^~_l+mJWucs_+*tE=e;f+Uq!gx(1hMHlzH(6H8;c+se@j!3HnJfX!!juHt&g?@J0BI!zxvn7 zp=G|O>xYCVd??FV<;`=Bd%*B?KC3<_<5ScD*p63H{P#(uQYhYX`THN}M=$zR!jLKXMF5&j^I+qQ;C6X=wLO3NVteuI#rDmM>+N6vblm>^ zpV=ny!^XMWm|4{SbjxN~UX=zD7YJuigcu)W*Wa)!UX(ofli)C(_47?p=_q=gI|JnHq$?-;{=YrXcTvc$z9Q1_K<>Q`dZw`bIE7`WOg!S zThf-tpZW61-}srSO3bdFhaH^)G&&|@U4?|<8lt@~Ldw03jR1CbL5%YfVs3#imXdU0!%z7}qpnLD5{B%j6 zdcz~8f44on`)>R6e^~$e&o|r24Y#`7@H#HGNSw`FhQ>2IY6G7tb#=v_qbpWbXjN=C z+ODOiu~;ATHEi8Jf*t2s?V1Ezt2IV3I#`=fZ4uZj_7zLkz^=0@NLf!Qah?bLP#&#^kn;*Qri>RC+tu@@ z+shZvx0kQqY~Q{5xc%|_o9*{(m3Vp13pgI$#tq`BSMrUC9fUFA&5;0qx6zD&rZ)n1o-fnvy{})Eyz;FAn(IE#oU?n`-*z~kz5;48o zf5C{9qNb1io3!T>2Gp)f3^rlrJjX%p1vF|kiZXbK7zcj-e?oakM8*o&{s-*h?V>GkGdY2UW{51`b`uH96CK&l-^t zKmtZ)UCCrqs=YW1z!S^V z4hGgX{vcyNXHD|qr)v-=d_h4PcFBo zmru86&k0~JUTkk(pKq_8U-JUmpLkT#Z@he#;APP8E((}9xGNRe$zk*i6a1eegMGHI z@DNK5*baZDCbOtB+U zs(`7ly|xu-K+?;&9<7jdt;UB8^XlcxhgyMX4}jpSp2&a)?>_9G;ij1sIsK!mj-61P zrx=RO$>|zt2|X~1s(s?qToIUqb6T=Djyak~&4B)~de;tQtIZqoquOWiTCkblajl0Wp4UP~u3z9FmBxv~@mZVu9VDbo-Lv^?G~w z@PbAAH`_f6_jm88%cE0H781#Hzs(X*!VEMqu`;5hBw1f0!4% zek+|A0JH6)#%4V`EVaKr(I7wqudZHUKQyqaB^n zXkdN*l{5Lt4zK$6qd>cY%UD0FI8|5!7`0W0wGfW4TVMT8g$~+Acy$NiM=e`uh9CJ0 zIeBFb)W8pjzk-Y$rn{e%3U(To4-p%_{(suK@%oSO`lHkyiCInY@UPJdY`=d==%~6<=(KvE$d#NzhTt%cDw!Q8B@ns+ucvR#rYRte3S}rWL6U?Dy&X3 z3`O$hV|sw2xWJ&iG+rh_yv5loVHlqBC*7rt zAG_)+e7!QJTC5JBdag2R7Lm%+Zr`Zri|BLU05Q#U z@q#^IXB^KjRsdu1{waa$2@Cj7E*+IUXRYh$c6Y<5nm17$dCq-I$0G|M>TL0QWI@d7 zpY1PO%4<=MZqJ2&45$Cu2^N?cAvp>gWdW9|#y}fD$na`0}atlHc=ksiKa>z0)_x zSVIfL6dKE;2GX%O7oU!z0bOLCH0PAXhz>y6e91!+n@{uiU_b!jVawmCt8S{JlaMx$ zSX0INGlG#;h+Po#OKYjg-<34ZEH3hOC@oh+;fNs}J&n^*)`*K@c>!M=K~aKhMJbi^ zOy?}OewK@72GAa z3eb1)^NvR;`5OD)e1j|8!k;$?>}x^sLp|-F4pV)$04Ml;mY137-YoKT%GyD6)wTKK z8>VM%#zM5Ey|j@h>I8OM`z%3THrJV?-ywwJQ%>bqUGcm4tPztZAM#r{X-{lyWdQp) z8W6^88=rCtN~P+OFJIcNGR!=(PMfjU_wC$HS!^Q)t_y4Gk`ktsPvn}g`KTQc| zz*rziaHbdUm4zl+BF-KWm@4LVs$W=78h&kmHcRs4;=IS&Xk{hGAB(6OMZZxJJ_Z$s z&jZUbpjAf(L8{T~*`T5j{RIaOjye`KBP?F|lB?bMG?t%80y>n<0R~GHs4QGcF?XQ8 zl&e(#q*T2UT4EaZDAG}zbR4mOLt17yeRDLmEFb=u!H9TE#uSf1jQq;|1;-vFBd6DZ zR`G0%IhQuDkn={G9+ekdbzb+3#0)yzA9T2Rfm51%Gl z#`lE4-=>ZWY&_I8^^ z(-w8(8i*vQJ8vDA()vVt%FEdcIM(Z@8nNRK5O2LkJTlU4Q{-7a6#P_QKB)nWV7ZBP zB+&7TZ|(XJhyEChV94vA6&`)WLisUU+_rQ-MYA9$`84==p?b$fcoLOw)t zLv(!TA%)a+txDn?>|C&7OxkYLTb&3T>t9_oQw&F8NlIp1Q(c2mrbqf`*i)CvYFQnJ zCwx1sw1a@;Ho#My;RlS0V3OXHa8$eS!A}8yaUrkWT>+haIY!9cmgzski;EAeCY$Ud zd-mV$sT_-rw(HH@MtJnRlpqn1YjlGaN;CLOHDLy}#zcjDTYmWDF`sCLK=`LM4DyU%?48gPaz7sIi zf|gTa)9OQ=NilL?O4UzFqkvBxrt-{be&UHWb7o;GH3zDSTIs4l#?7d$nEM|GZs3{0EQAp)Y~p89F~@O5cqbjIQiBq@7(gaDxT(XGiJ? z*e8sz5n0{^60WbEBOtjZZN$8;h%YD66K#$%mQIx)pgM|mTqs9&+Zp^>W%!YhG}Bc6 zR|YWZ&Dk7PqHDey?N|DIgxNp4O+7bz);+%f$kUX8gXGur?S9cOOy~ih9AM+jeDV2u z4KX#XG(zczG{Bi&&UsEMeD!dm@W8Qw3Xr*~X&U_EPy}If0*)#}4XRno$X7t4^+1zn zk+?i?vNr(F$)JcFfq*eRc*bfrDcROS;pKN9^_ZJWMLCGR(n|=h}fuwAk=z^b|9@7>b>3%!}!{)c^c^41yE4enSl!!e#AHLGh~zi zkFXK%j=U~tOg`J{jy)X*7NqaE0(QzUV4k=jF&`D9Oz>|@Ko6tyvBm^6bvBxEX+q7< zueu_``Zj{HffENs`qdX)z)-HMjCTwct4RGHu67Rk@(tZM+Lt=eN_M%(#)AO_gHU`B z0!<PSK}4df@|p9Z5Oba-?;^7NdQn)fNka*SSw%bvO&$$S>22Qw0Mvn_ITe4^OvW zAKq-2xBrho#sWY1&l}xhYd0J-z;=DB!7oXfDlmv5?&i=HSJza<4M27vsed3k@;ZC) zJWVazgHAsXMujxWgGbIoFCpR83m*ef`lqyV@ugK;q;Oiw_5nVKSvFTTtzW3*NUo>h zw9ddu2kZ;~6iZi{h7C%nDgB+c=lo#{@i78J(FzYh3zAk+Vt9%#%BDvhWR|&x()Ni6 znD56E%Z8blRjzfe@t1lQylf#{^ejPiM%<0D9?6n@wF|aAIwz~p#!+jf##N;MWlCWwOXcz=&!Sp2YK$36`%f7p z2Sl*2dbE;ug^Ed?xMQ_oXZ&Y60BT7`UZOK;;e~d|MBhJU6C`12rh>dJ>g#D@0Zhd? zowO}6x^i9Yk-kA?$0uT7+@N|*=6m8CZ#dXq-LW3VXzY@`fET=F>f9&0@|4zt-#S;_ zaug$EUK|jKGB8S+o%cQ)OveKs{Ml0-m?|8uU$SUV@cM=6_>`bYlfD{@P3G}0OK?C-3sAo%Mq0e@~2D@FWeeYK2ya;Ti~X=P66DG#Ds6Z zAEQ_EkrtX@=G&q_Be#22?LT3n_jF#b?q6=N*)sEr{b1*ghPcSgAe(6<{308D2{g!~ z$Tr|uzYJ#u-0#|&mvjsg`m;wrrh(Njt8zz?m8Y#^nO4&b*aqo_T9y!c>@c-;cmtKA@O$@<8HkyC8uVN*YXL|429@~GC3R8r zDCWloDTnf?ucUj@ebBq+*>#sZ6xp*+IVZr_dl$$^KL#%W^i9`7l4ap8NEoH|jh zfB*(gW5CUk=eTj3*zGBu*T!SV^^F_@1vsVT+);+zlUkY%S2UylmUdK-`tnOsy}EJO zj#huj?j*ylytyfQ(U8ET59!LEaQ=|5&FZwtS5EQN5ClUrrc!68QIs^IVzKqo}VIWFJM8b0tX^M0gHyF{ zARTCV6@f?dO)sQ0>SSPg=K_9LGIc|R=FYeKYj)UwVwClXm)5@j{dRo!J+DZ6wQV1G zme*%OJYX))`Z>oA4Ydg*>5o^^bk)p}5sT_>4>9m4zpX+FGwPHhcvBYzWXlDTPod05 zHf^77MmiLiP3u&E_qMT-)4K4341_H7$f$(rj*ZF%{WF4>0nGG)#cE&VJI{w#t9*yC zK_X`wVDU3WQjs@<0SDPr|5QwEMnCa{f!|5wpSwgJWnOn1`wKMut2?g?sS8Kc;Nr<298Ft63i@68?;PC8MF9flUDkKO-^G91gR-#J zwei;VhqG6_%i!DX1|PrPK5gu(fOnS$w6E)qJ@=}W1fOidz%JiaE}6x>3PxTC(8V0J z8O*Y+fyqvUBZeY)SqHhzQ~%(u4NxbZ$!m6{&P?lq@`|2duc3#ysVM}~nGz*O`RyR)@G#>Am( z6!bV}-t)-xp=W{_hg=unyxv(PO2y%ft1f43((`3n5;Xt-5^PCCK~%h_0YH4#)^tY5 z%B#jzDVA}rquKC6mc~h|C>chZtykp9ipn2!6mEnB6j?yor`}pd>|O|T1vpErN z>^Gj}b;6YKJl}Q)4c2fRB#B$KhZ$);*+g$7sied}LU9 zb1Otnv>Zuaoib&T_gQ@J3S2f2K-=hoays00%a3YFu_^@l4R9m@0jD<8j_ymczAtQ} zf!R>_J}}xdFgdk!s_E!V9d)!3t)NB%7-~}P-2$^=*zzpxK`ln#bfbk>(8e;u*G3{- zupYFP{wK^Tl2=ZZPo&XTnj zGYoh)gS^&Z$RGdmWWe%jrB^6@QV6@n;?I}fo-qQu-`)_ozS({|e&khoJhei9!;g9p zCXi^5RR_n|KA`E8Q5#;a&I~>fxgh<-o%xJKy3)9UQLaIdBo>knV|IY(+yP#UlBXSY zV|gvX$jv6*tiw9kIG2J(!Cu_KYBOymF#P%DZ|=p#*UU^AO;;^5pah(?-1TkDV<)MRQ{Aj5EDU4oT- z>N(4}=698|n-N_z;`v+Lg#EzC>yC}7$9JrKvBUoUCq`r+p7f^eo7=m#FchGWL7WGk zHqmHCe2lzYXipX$f-fqBC;5${EXJcMFrcK5xGaPEXi7ejNe4IJD#7R|Es};Z^6MW4 zz|oz40X&Gi0<6g-q5*-bz!u@Dw&h#7!A$Q#RH&WLRMPn(xXfpRE}c?Nfndb$@+d=dz^i5B zi}|k4WL2+@oY0-99tbLJo6%zOqq>`Ja$eApD)92A-du~b|C9~R3v8Czf%skbR9cRs zrDbI`=oQFFdqvv`^FAdOwx6d8EOx(Z`` z`S(kj_~R0R8=np*#vjiac!xf5mZ*P}ilSwldB zJyI7XH608Iiji6QGvJx>pT^P=hNDZK29`OQmFM_ZD)S7Q1{8yrU&^x><FwX5*>K*wF10WzhJ9Q4V>v|I}XE(vS2-m13}k)~uZr zM5zM7a6{X=^BMj7L<&FpfK#2)EKg{M+5JAeX=i;+Mh`e(dFBXw zqjhP^dtZW-zUQm}*&1;buo5zP#OxN6MwH@rP^FZ@o0KX+Ej}ORhaQnYUaarm1N7&d z97azC#T9Hs6n%pxgRXP$dBlcCI)3kdIC8VMYhc&MS43;;*JumdI z^F}EJLr9bKzQ+qY1J*(v@j2?cq%k`Ro4XuFLpE>i?^(y?E8dJv&HO?+^#^%Qm(a02 zPtY=Nf`v6)O%j_U&VnX-2AcW{9(sC>Ev7t&dOf#+YNsuzBe@nB=%^2;(1zZWE z5RF>w7zt2}4kN#ms7#qx=`o~btSYA{Kt?JdA7dF@oq}ad>KXMlkV1NGY=<4iQeXVQ zGzB%F*l-Pk9zCyx3u40?gZe@Z;Tn}_6mU3lDtI899K|1XZKN|A)My42&xcODpqP5} zYEcvY^>`XaQAhUw7{reE+?dUyp|3wP8e@a%DM9VPqjN@1;K5O6fb8Ow-qAG^Xeoz_ z9jaEnO4~FH_I1S4c|Xzz#$^CmR4;rrMs{#iDSQrT&`_^b?B5^ZM6`5aJg$gSzGN+m+@}mrstJaZ6zav0e zMxgX6pn>dc-@9dirUg1VnNjS(i%vZHWmghCQ@_APLAj`P$Ud6?j*X)Aol zu`sPEg4iV&*8d4!9C-^jGuV!IM6!GMygem=ZLfK&)M5L0e9vZ^UkUWT!kBL>`b-d$ z9;deAVUKMVaA5Fqz(DZwp4PogRmq*#da^b8v}3^2vq!deS=XhOv`wcLZ4o+4h3mbgB2WGdi#1sb3V zUGN=m#{SGzMz&3C?Eg6-khzDIxepKwk-KWwJu9f?en^JUHz6YeTA{g0pV znd(YY-ujMz6j;Q)dUxM!O`8V>*yI^+b6(<~EKtmW1Ybj#xGABIIx^w^Fk&4WacgI! zqit|NUPoW7(PV>5-k^SQ#?Jdw_5=3RT(+t>7~uTC&E2;kzCC$MFnbToN96z9V|LES z(T^R;st+5d_VCK29a4&fOZC@t7FZaZqz>!ZX8F)dXU+gdUP{$MC z^fmBU1`Y?sx4ONbU^q&b^4iW<#<9H+8m8RvQ8d^EvKw;ZuxCvB=9Fs0El`UsZF zsYR|GyEIRk?cQEZ-w?OR6q#R={*J@vfE2e1r5?fvPJICT7tr#D@dbTK_Pns*OMIt> zv`FRrq2OmQMUS-G8Y-`beDGOv%EL@m(F0@@k%7<1n4kg+4o}H7_3N;51+AdZA}{cD z#w7A%brez%F!Zc5w~l!GU`ra)iKFW~9t(cX4f;I&R$RU?su#Dw%Yh38cZX-&=hK%2 zrZ?!4i#zu}VPkyao;P+QkWUerphIWmFn6CZ7>#Wg8bJQE2Q#FL7K`Tm+#UU1V6aT( zs1pOByebQMR`(-rFUSb!82^HNNoa3xCUG$~7uJaL%Th9vBlYP1(HRM=huo9o9yndu6EL%?9OspNcd#>s(z#f{nr7VqzWW&P{Di}viMXAjtk zyWE*JImbW4Fi1xw1Etd_7aa~k%qfa%w+86bbKc(xjR!if zJMyoOT!lV<1Csan!QV+!$EhSi!-syy2c4WRD0WnmXpE)~JczQFa=>dg@sZ)=H>%Y3 zxRO^qlOJAX*y`0UVICO^r-98;{RA?my~_ZPui;a)M|&sjC_tQkJl2dzq)=?APQ@>{ zUt#z{X1qfhi;l(dQeycj>1={6*)rV@6#bSfyaWN@>y1f;gN&TksE_hYqXEhYCY)&A zal|=_x?rRsbrySOnSlBBcZ^rWpo)>+E}%I5WPBQ zm%b6q9_dH`IKkMB?)28WJ@PfGauo!zZbp*daT})utxeM15 zWP}w)IlyT>A28@uCLW=4>La1u>qEfV?zrOgiA}2pFYY5bejt8+hA(a(aXN5%>nN6& zopS%sJbuUE<7g19UJi%M!9{-FTJzwhM_GpIIHWp28@48nim1al#C8g4dLHqtt+Y}9 z=n6g3;nCkXv7v2ve^BrY^`VpCYF*>N8Uzf9rSIQQ8Ao zpBEbVaNUri0y+GfHo*IlZzX6ZQjS03fTjW{Zw-u%u#CLU&wWzGWwx;Udjj1vI;lYE zyqx)Hkq4%fH%D&Z=jx>23!d0|&Miha!0Ji*)d4^Bt=tBrreOH3OANdXAzn}2j|pvE z-u+(2GR%vb;Yo$!N2eFq#xy=&U_&^sNFJD*+EKQgO`RS)j!4-7W*dM|4P_>-I!Fdi zHA*@`502X0Qln2z8>L(uD}(-QCH3X8dJxlGfCx3P+HvxX8*@=k#RKc~8zY<_KYpT9 zfaztg*v967T*;$xmm%0{gIIJD-+QQD53h4kx3aH#S_sq)$Q%C%r% zsWds@t2BC~Y4pBE&H(4ELcLnvh=MxA0*R;E+52VKFlbIJHz;A8Me=EtlrXI7gVmtvHgaUf}cgn&lS!lw<+d zqwCJjUaa@vh+IC&zh-^(_LfJMlaF|mLm`K#l4_&gSd3IS1(wd&_D0*)76T>Gv6CEq tu8a;2J(NOhx&}JpoY^osaTCR1`!DZ7VGsD>F~k4>002ovPDHLkV1hH8G^GFl diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 4ba0ac5..b96e79b 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -29,7 +29,7 @@ class HomeViewController: UIViewController { private let contentStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical - stackView.spacing = 40 + stackView.spacing = 20 return stackView }() @@ -58,6 +58,12 @@ class HomeViewController: UIViewController { return viewController }() + private let backButton: UIBarButtonItem = { + let barButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + barButton.tintColor = .black + return barButton + }() + private let viewModel: HomeViewModelProtocol private let disposeBag = DisposeBag() private var topSafeArea: CGFloat = 0 @@ -121,11 +127,11 @@ class HomeViewController: UIViewController { ) .withUnretained(self) .bind(onNext: { vc, _ in + vc.navigationItem.backBarButtonItem = vc.backButton let viewController = WhatsNewListViewController(viewModel: WhatsNewListViewModel()) vc.navigationController?.pushViewController(viewController, animated: true) }) .disposed(by: disposeBag) - } private func attribute() { diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift index d21feeb..1f30b6e 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift @@ -14,7 +14,7 @@ class MainEventViewController: UIViewController { let imageView = UIImageView() imageView.clipsToBounds = true imageView.contentMode = .scaleAspectFit - imageView.backgroundColor = .red + imageView.backgroundColor = .black.withAlphaComponent(0.5) return imageView }() diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift index 17ba118..5461810 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift @@ -57,6 +57,7 @@ class RecommandMenuCellView: UICollectionViewCell { } func setName(_ name: NSMutableAttributedString) { + nameLabel.backgroundColor = .white nameLabel.attributedText = name } @@ -73,4 +74,8 @@ class RecommandMenuCellView: UICollectionViewCell { }) .disposed(by: disposeBag) } + + func emptyCell() { + thumbnailView.backgroundColor = .black.withAlphaComponent(0.1) + } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift index 03726f6..07193e4 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift @@ -28,15 +28,20 @@ class RecommandMenuDataSource: NSObject, UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - products.count + products.isEmpty ? 3 : products.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecommandMenuCellView.identifier, for: indexPath) as? RecommandMenuCellView else { return UICollectionViewCell() } - let index = indexPath.item + + if index >= products.count { + cell.emptyCell() + return cell + } + let product = products[index] let image = index >= productimages.count ? nil : productimages[index].first diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift index 86885a0..7ddc2ee 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift @@ -22,6 +22,7 @@ class RecommandMenuViewController: UIViewController { label.textAlignment = .left label.font = .systemFont(ofSize: 24, weight: .bold) label.textColor = .black + label.backgroundColor = .black.withAlphaComponent(0.1) return label }() @@ -76,6 +77,7 @@ class RecommandMenuViewController: UIViewController { viewModel.state().displayTitle .withUnretained(self) .bind(onNext: { vc, title in + vc.titleLabel.backgroundColor = .white vc.titleLabel.attributedText = title }) .disposed(by: disposeBag) @@ -96,7 +98,7 @@ class RecommandMenuViewController: UIViewController { } titleLabel.snp.makeConstraints { - $0.top.equalToSuperview().offset(20) + $0.top.equalToSuperview().offset(10) $0.leading.equalToSuperview().offset(20) $0.trailing.equalToSuperview() } diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift index d841693..101fcf4 100644 --- a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift @@ -10,6 +10,12 @@ import UIKit class WhatsNewListViewController: UIViewController { + private let navigationView: UIView = { + let view = UIView() + view.backgroundColor = .white + return view + }() + private let tableView: UITableView = { let tableView = UITableView() tableView.register(WhatsNewListViewCell.self, forCellReuseIdentifier: WhatsNewListViewCell.identifier) @@ -40,14 +46,6 @@ class WhatsNewListViewController: UIViewController { .bind(to: viewModel.action().loadEvents) .disposed(by: disposeBag) - rx.viewDidAppear - .map { _ in } - .withUnretained(self) - .bind(onNext: { vc, _ in - vc.navigationController?.isNavigationBarHidden = false - }) - .disposed(by: disposeBag) - viewModel.state().loadedEvents .withUnretained(self) .bind(onNext: { vc, events in @@ -60,12 +58,19 @@ class WhatsNewListViewController: UIViewController { private func attribute() { hidesBottomBarWhenPushed = true view.backgroundColor = .white + title = "What's New" tableView.dataSource = dataSource } private func layout() { view.addSubview(tableView) + view.addSubview(navigationView) + + navigationView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.top) + } tableView.snp.makeConstraints { $0.edges.equalToSuperview() From d4b196d097579ac0180a871e2987bfcb08c5a617 Mon Sep 17 00:00:00 2001 From: shingha Date: Tue, 17 May 2022 22:57:56 +0900 Subject: [PATCH 38/57] =?UTF-8?q?[STAR-22,=2023,=2034]=20feature:=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=ED=99=94=EB=A9=B4=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EA=B5=AC=ED=98=84,=20=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9,=20=EB=A8=B8=EC=8B=A0=EB=9F=AC=EB=8B=9D,=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80,=20=EC=B6=A9=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 100 ++++++++- Starbucks/Starbucks/.DS_Store | Bin 6148 -> 6148 bytes .../Starbucks/Resource/KoreaCash.mlmodel | Bin 0 -> 49948 bytes .../Starbucks/Sources/API/Base/APIError.swift | 6 +- .../Starbucks/Sources/API/Base/Response.swift | 14 +- .../Sources/Common/Camera/CameraSession.swift | 92 ++++++++ .../Common/Camera/CameraSessionError.swift | 12 ++ .../Sources/Common/Camera/PreviewView.swift | 19 ++ .../Starbucks/Sources/Common/Container.swift | 4 + .../Common/PropertyWrapper/UserDefault.swift | 37 ++++ .../Starbucks/Sources/Common/UserStore.swift | 36 ++++ .../Sources/Model/StarbucksEntity.swift | 32 +++ .../Present/Common/NavigationBarView.swift | 64 ++++++ .../Present/Home/HomeViewController.swift | 7 +- .../Sources/Present/Home/HomeViewModel.swift | 31 +-- .../View/MainEvent/MainEventViewModel.swift | 15 +- .../Recommand/RecommandMenuDataSource.swift | 2 +- .../Home/View/WhatsNew/WhatsNewCellView.swift | 3 +- .../View/WhatsNew/WhatsNewDataSource.swift | 2 +- .../WhatsNew/WhatsNewViewController.swift | 12 +- .../View/WhatsNew/WhatsNewViewModel.swift | 10 +- .../Present/Pay/PayViewController.swift | 193 +++++++++++++++++ .../Sources/Present/Pay/PayViewModel.swift | 108 ++++++++++ .../Pay/View/CardList/CardListViewCell.swift | 198 ++++++++++++++++++ .../CardList/CardListViewController.swift | 91 ++++++++ .../CardList/CardListViewDataSource.swift | 39 ++++ .../TabBar/StarbucksViewController.swift | 2 +- .../WhatsNewListViewController.swift | 12 +- .../WhatsNewList/WhatsNewListViewModel.swift | 11 +- .../Repository/Camera/CameraRepository.swift | 16 ++ .../Camera/CameraRepositoryImpl.swift | 45 ++++ .../Starbucks/StarbucksRepositoryImpl.swift | 16 +- 32 files changed, 1150 insertions(+), 79 deletions(-) create mode 100644 Starbucks/Starbucks/Resource/KoreaCash.mlmodel create mode 100644 Starbucks/Starbucks/Sources/Common/Camera/CameraSession.swift create mode 100644 Starbucks/Starbucks/Sources/Common/Camera/CameraSessionError.swift create mode 100644 Starbucks/Starbucks/Sources/Common/Camera/PreviewView.swift create mode 100644 Starbucks/Starbucks/Sources/Common/PropertyWrapper/UserDefault.swift create mode 100644 Starbucks/Starbucks/Sources/Common/UserStore.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/PayViewModel.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewCell.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewController.swift create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewDataSource.swift create mode 100644 Starbucks/Starbucks/Sources/Repository/Camera/CameraRepository.swift create mode 100644 Starbucks/Starbucks/Sources/Repository/Camera/CameraRepositoryImpl.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index f4b9110..a373718 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -13,6 +13,20 @@ E0039B58282FD93200298719 /* WhatsNewListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0039B57282FD93200298719 /* WhatsNewListViewModel.swift */; }; E0108CD4282FEE4D00CC736C /* WhatsNewListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CD3282FEE4D00CC736C /* WhatsNewListDataSource.swift */; }; E0108CD6282FEF4A00CC736C /* WhatsNewListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CD5282FEF4A00CC736C /* WhatsNewListViewCell.swift */; }; + E0108CDA283128CD00CC736C /* PayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CD9283128CD00CC736C /* PayViewModel.swift */; }; + E0108CDC28312EED00CC736C /* CameraRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CDB28312EED00CC736C /* CameraRepository.swift */; }; + E0108CDF28312F0E00CC736C /* CameraRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CDE28312F0E00CC736C /* CameraRepositoryImpl.swift */; }; + E0108CE22831314800CC736C /* CameraSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CE12831314800CC736C /* CameraSession.swift */; }; + E0108CE42831E8D600CC736C /* CameraSessionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CE32831E8D600CC736C /* CameraSessionError.swift */; }; + E0108CE62832558A00CC736C /* KoreaCash.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = E0108CE52832558A00CC736C /* KoreaCash.mlmodel */; }; + E0108CE8283260C200CC736C /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CE7283260C200CC736C /* PreviewView.swift */; }; + E0108CEC28333C5100CC736C /* CardListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CEB28333C5100CC736C /* CardListViewController.swift */; }; + E0108CF02833403900CC736C /* CardListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CEF2833403900CC736C /* CardListViewCell.swift */; }; + E0108CF2283364F200CC736C /* CardListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CF1283364F200CC736C /* CardListViewModel.swift */; }; + E0108CF42833658200CC736C /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CF32833658200CC736C /* UserDefault.swift */; }; + E0108CF6283365CE00CC736C /* UserStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CF5283365CE00CC736C /* UserStore.swift */; }; + E0108CF82833784800CC736C /* CardListViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CF72833784800CC736C /* CardListViewDataSource.swift */; }; + E0108CFB28338D3A00CC736C /* NavigationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CFA28338D3A00CC736C /* NavigationBarView.swift */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; E0723058282A13C900AF3E16 /* StarbuckTargetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */; }; E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723381282B7DB900AF3E16 /* HomeViewModel.swift */; }; @@ -84,6 +98,20 @@ E0039B57282FD93200298719 /* WhatsNewListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewListViewModel.swift; sourceTree = ""; }; E0108CD3282FEE4D00CC736C /* WhatsNewListDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewListDataSource.swift; sourceTree = ""; }; E0108CD5282FEF4A00CC736C /* WhatsNewListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewListViewCell.swift; sourceTree = ""; }; + E0108CD9283128CD00CC736C /* PayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayViewModel.swift; sourceTree = ""; }; + E0108CDB28312EED00CC736C /* CameraRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraRepository.swift; sourceTree = ""; }; + E0108CDE28312F0E00CC736C /* CameraRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraRepositoryImpl.swift; sourceTree = ""; }; + E0108CE12831314800CC736C /* CameraSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSession.swift; sourceTree = ""; }; + E0108CE32831E8D600CC736C /* CameraSessionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionError.swift; sourceTree = ""; }; + E0108CE52832558A00CC736C /* KoreaCash.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = KoreaCash.mlmodel; sourceTree = ""; }; + E0108CE7283260C200CC736C /* PreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = ""; }; + E0108CEB28333C5100CC736C /* CardListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardListViewController.swift; sourceTree = ""; }; + E0108CEF2833403900CC736C /* CardListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardListViewCell.swift; sourceTree = ""; }; + E0108CF1283364F200CC736C /* CardListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardListViewModel.swift; sourceTree = ""; }; + E0108CF32833658200CC736C /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = ""; }; + E0108CF5283365CE00CC736C /* UserStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStore.swift; sourceTree = ""; }; + E0108CF72833784800CC736C /* CardListViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardListViewDataSource.swift; sourceTree = ""; }; + E0108CFA28338D3A00CC736C /* NavigationBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarView.swift; sourceTree = ""; }; E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -189,6 +217,52 @@ path = View; sourceTree = ""; }; + E0108CDD28312F0000CC736C /* Camera */ = { + isa = PBXGroup; + children = ( + E0108CDB28312EED00CC736C /* CameraRepository.swift */, + E0108CDE28312F0E00CC736C /* CameraRepositoryImpl.swift */, + ); + path = Camera; + sourceTree = ""; + }; + E0108CE02831310E00CC736C /* Camera */ = { + isa = PBXGroup; + children = ( + E0108CE12831314800CC736C /* CameraSession.swift */, + E0108CE32831E8D600CC736C /* CameraSessionError.swift */, + E0108CE7283260C200CC736C /* PreviewView.swift */, + ); + path = Camera; + sourceTree = ""; + }; + E0108CE928333C3900CC736C /* View */ = { + isa = PBXGroup; + children = ( + E0108CEA28333C4200CC736C /* CardList */, + ); + path = View; + sourceTree = ""; + }; + E0108CEA28333C4200CC736C /* CardList */ = { + isa = PBXGroup; + children = ( + E0108CEF2833403900CC736C /* CardListViewCell.swift */, + E0108CF72833784800CC736C /* CardListViewDataSource.swift */, + E0108CEB28333C5100CC736C /* CardListViewController.swift */, + E0108CF1283364F200CC736C /* CardListViewModel.swift */, + ); + path = CardList; + sourceTree = ""; + }; + E0108CF928338D2800CC736C /* Common */ = { + isa = PBXGroup; + children = ( + E0108CFA28338D3A00CC736C /* NavigationBarView.swift */, + ); + path = Common; + sourceTree = ""; + }; E01B526D28289D17009918AE = { isa = PBXGroup; children = ( @@ -222,6 +296,7 @@ E01B528F2828A0C3009918AE /* Resource */ = { isa = PBXGroup; children = ( + E0108CE52832558A00CC736C /* KoreaCash.mlmodel */, E01B528228289D18009918AE /* Assets.xcassets */, E07233C9282B82BF00AF3E16 /* Category.json */, ); @@ -262,14 +337,15 @@ E072337F282B7DB900AF3E16 /* Present */ = { isa = PBXGroup; children = ( + E0108CF928338D2800CC736C /* Common */, E0723386282B7DB900AF3E16 /* TabBar */, E0723380282B7DB900AF3E16 /* Home */, E0039B54282FD8C000298719 /* WhatsNewList */, E0723384282B7DB900AF3E16 /* Pay */, - E0723383282B7DB900AF3E16 /* RootWindow.swift */, E0723388282B7DB900AF3E16 /* OrderDetail */, E072338A282B7DB900AF3E16 /* OrderList */, E072338C282B7DB900AF3E16 /* OrderCategory */, + E0723383282B7DB900AF3E16 /* RootWindow.swift */, ); path = Present; sourceTree = ""; @@ -287,7 +363,9 @@ E0723384282B7DB900AF3E16 /* Pay */ = { isa = PBXGroup; children = ( + E0108CE928333C3900CC736C /* View */, E0723385282B7DB900AF3E16 /* PayViewController.swift */, + E0108CD9283128CD00CC736C /* PayViewModel.swift */, ); path = Pay; sourceTree = ""; @@ -331,6 +409,7 @@ E0723392282B7DB900AF3E16 /* Repository */ = { isa = PBXGroup; children = ( + E0108CDD28312F0000CC736C /* Camera */, E0723393282B7DB900AF3E16 /* Starbucks */, E0723396282B7DB900AF3E16 /* NetworkRepository.swift */, ); @@ -359,11 +438,13 @@ E072339A282B7DB900AF3E16 /* Common */ = { isa = PBXGroup; children = ( + E0108CE02831310E00CC736C /* Camera */, E07233F6282D515800AF3E16 /* View */, E07233D2282BCBA900AF3E16 /* PropertyWrapper */, E072339B282B7DB900AF3E16 /* Log.swift */, E07233D0282BCB5E00AF3E16 /* Container.swift */, E07233D5282BCC8100AF3E16 /* ImageManager.swift */, + E0108CF5283365CE00CC736C /* UserStore.swift */, ); path = Common; sourceTree = ""; @@ -403,6 +484,7 @@ isa = PBXGroup; children = ( E07233D3282BCBBC00AF3E16 /* Inject.swift */, + E0108CF32833658200CC736C /* UserDefault.swift */, ); path = PropertyWrapper; sourceTree = ""; @@ -692,10 +774,15 @@ E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */, E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */, E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, + E0108CF82833784800CC736C /* CardListViewDataSource.swift in Sources */, E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */, + E0108CEC28333C5100CC736C /* CardListViewController.swift in Sources */, E07233C5282B7DB900AF3E16 /* AppDelegate.swift in Sources */, + E0108CDA283128CD00CC736C /* PayViewModel.swift in Sources */, E07233DB282BD71900AF3E16 /* RecommandMenuCellView.swift in Sources */, + E0108CE8283260C200CC736C /* PreviewView.swift in Sources */, E07233BE282B7DB900AF3E16 /* StarbucksTarget.swift in Sources */, + E0108CDC28312EED00CC736C /* CameraRepository.swift in Sources */, E07233C1282B7DB900AF3E16 /* Provider.swift in Sources */, E07233F5282D3BF000AF3E16 /* HomeInfoView.swift in Sources */, E07233D1282BCB5E00AF3E16 /* Container.swift in Sources */, @@ -703,10 +790,12 @@ E07233B1282B7DB900AF3E16 /* OrderTableViewDelegate.swift in Sources */, E07233B7282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift in Sources */, E07233D6282BCC8100AF3E16 /* ImageManager.swift in Sources */, + E0108CF6283365CE00CC736C /* UserStore.swift in Sources */, E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */, E07233DE282BDDB200AF3E16 /* RecommandMenuDataSource.swift in Sources */, E07233F8282D516200AF3E16 /* GradientView.swift in Sources */, E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, + E0108CFB28338D3A00CC736C /* NavigationBarView.swift in Sources */, E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */, E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */, E07233AC282B7DB900AF3E16 /* RootWindow.swift in Sources */, @@ -715,13 +804,18 @@ E07233B5282B7DB900AF3E16 /* OrderViewModel.swift in Sources */, E07233EC282D0BE500AF3E16 /* RecommandMenuViewModel.swift in Sources */, E07233D9282BD60200AF3E16 /* RecommandMenuViewController.swift in Sources */, + E0108CF42833658200CC736C /* UserDefault.swift in Sources */, E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */, + E0108CE62832558A00CC736C /* KoreaCash.mlmodel in Sources */, E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, E07233EE282D0F9300AF3E16 /* WhatsNewDataSource.swift in Sources */, E0108CD4282FEE4D00CC736C /* WhatsNewListDataSource.swift in Sources */, E07233E6282D039600AF3E16 /* WhatsNewViewController.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, + E0108CE42831E8D600CC736C /* CameraSessionError.swift in Sources */, + E0108CF2283364F200CC736C /* CardListViewModel.swift in Sources */, E07233F2282D235100AF3E16 /* NSMutableAttributedString+Extension.swift in Sources */, + E0108CF02833403900CC736C /* CardListViewCell.swift in Sources */, E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */, E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */, E0039B58282FD93200298719 /* WhatsNewListViewModel.swift in Sources */, @@ -729,10 +823,12 @@ E07233B4282B7DB900AF3E16 /* CategoryTableViewCell.swift in Sources */, E07233E8282D03A000AF3E16 /* WhatsNewViewModel.swift in Sources */, E07233EA282D075B00AF3E16 /* MainEventViewModel.swift in Sources */, + E0108CE22831314800CC736C /* CameraSession.swift in Sources */, E07233AE282B7DB900AF3E16 /* StarbucksViewController.swift in Sources */, E07233AB282B7DB900AF3E16 /* HomeViewController.swift in Sources */, E07233AD282B7DB900AF3E16 /* PayViewController.swift in Sources */, E07233B3282B7DB900AF3E16 /* OrderCategoryViewController.swift in Sources */, + E0108CDF28312F0E00CC736C /* CameraRepositoryImpl.swift in Sources */, E07233B6282B7DB900AF3E16 /* StarbucksRepository.swift in Sources */, E0108CD6282FEF4A00CC736C /* WhatsNewListViewCell.swift in Sources */, E07233BF282B7DB900AF3E16 /* APIError.swift in Sources */, @@ -884,6 +980,7 @@ DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Starbucks/Configuration/Info.plist; + INFOPLIST_KEY_NSCameraUsageDescription = "카메라 사용합니다!"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; @@ -913,6 +1010,7 @@ DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Starbucks/Configuration/Info.plist; + INFOPLIST_KEY_NSCameraUsageDescription = "카메라 사용합니다!"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; diff --git a/Starbucks/Starbucks/.DS_Store b/Starbucks/Starbucks/.DS_Store index 65316e80e6698ecc43a6dac3fa5338bd6ee0288f..7f9048191772585b800e12f2ef57a871620fd9d8 100644 GIT binary patch delta 108 zcmZoMXfc=|#>CJzu~2NHo}wrt0|NsP3otO0G88c+Go+RU7v)VXR0m6NFa$BAG88l9 y!z4E!V0LHO{D4`3c{4i)KL^m5%@>)!Gf(Ch(d7i0bO4AMCfo2xZ;laJ!VCZx9T;W+ literal 6148 zcmeHK!EVz)5S>j!v!SY#14z9fS>jriKue{Hi%H9&*BZeAP_P?QEQ~jb9dd{w`P_aC zSH6Vbfj7GwAP%6O5P}(L_N`~%v$M~R*GoidFdf||>JgDfGPb&CeiA&+xfU&7*#s(k zj*Kp7N)w8yT4PJ$Z*+j~-8LPQp@QDfkM}R*+G%diliVa=@gwpWx%b5edPFzr)anfF z(U@}ROR5^0j}48cGfKfT_DMOXbd|5}H258gzH_N;u&;j4PG;rU|Nc+OIM2$e-~X&y z8|@pL9o^Ae`c?GS%%Uo;rsW`>yx^m!#^lMeA1BYUbT;hWIyOZWXGJ=(f^3w6^746B zjLd9crp2hV+Q<#)uI>(dJM;O&{iEIB;P7Cv8_bU$^wHlxTr9f!_T77r&W7*u_l3E{ z3=j(cARF5rm%uY@c<>>b6uBubFtSuF*@EJLI3NyO#{vKD&|B9b3r8Ff2mUJu`27$d z8AFe?L;LDLW48dnHp({8=MP2J1Rg_=wL^>`l#>FTRQWB2a&puI&kH@)4xOBo-+U;4 zW#xA$%3dAk4=kNj=uld5Kpa?gV8b2uc>RC!>;3<#NRGq-ap0eFK($YzlL4mW*VeVk v@md=oUn4n~S3CSBLB}q|;PO(ugS3Hqz#U-dv37_GLOukn4bq4Mf7F4mSH^+) diff --git a/Starbucks/Starbucks/Resource/KoreaCash.mlmodel b/Starbucks/Starbucks/Resource/KoreaCash.mlmodel new file mode 100644 index 0000000000000000000000000000000000000000..d0ede277607e3c22e0b1e98fe741063c0dc0e725 GIT binary patch literal 49948 zcmc$FXHb+uw55n5prU}1Q4|#v2_h&au8N?dk`x6b=bS+hL69Ut$w?%GAtfB8BS^Zyvp$V5}ikjKd^0Zq?Fsl_ZxCj8q@zrv&2A2X`!KItYvDhXKcwtd4ZgAujA39m5%%T z?f1u$>?J3m+;So%C!zUFLivB~`u~5b-!Zm3l2W=jl2AH2k`$d=Ao;7;juR2?&lrq` zu+?+v_V=Cu)V%6E7~B#KKi0neJuSZsu@vvge~l8 z=9|Eg&ZWe->MfFa20whzUWfWQIo=1Vd!Tl+DCL8B1^!7S_ysEZz<4*65W%Gv=(Qfm zQM+>M&{XD_mqZZ(lNY@DGGKi*KeQd{&f4Xm!eC&lE-D4gu-sRvj zHBaAZ!(QJSJHeV=UVSUgMaA9%?pQ)HM3 zz=vM>+vmpfpdqn}_vYs=y!ygP@QP6l*4)kS(3m1%?`vzXknU(adEn25Z+2a9(97@R zqH;0}CgOem_zK)|>vVWf=MTA~)<2Iu_Q0EjuJMM;V^G0;?6R475xf)~aSB^1M2?oh z_6|Y~I@n7-ocFB45crzTmazq&wG8*u@YcZHmbCu)mwgx{`zT(*um|e2FMFKN`hbRY z18&;SZ6TFDozc6m2F^{k&V-xQqQQ{(Q4Y!;OysSTwppHl2y@%$X0;+rIFNk!!=GuG zC?sK1s!4*BcHgJfD-n1x${M1%3w|5abPE;u z1*N3134T?6kl7q~WmtX?9KXje9MSH_+YVEw8mVI8uWr|q=LdR_W!e6oFi90`XAE@= zRMY`$&7g<(vsV06vDDJ6Jc!C#g8XN*TQPng{yf{2Btlz}Zu-N^7lKK+QvGIfM7JM|&by~E^0-3vH4O&&k#1OYZkuk?uK|+P z(!@>WPS_s1?(hK$q2S=7$MFve@SsnLS-gA~)Zm8-*ZvL&K6U%cLz8msP)u=iINAgt zm(QU1GaNtm`_Z~?^g)i=`R^}FJAr}hN`ksM0a73TOSiF%2D$FWecZ~`5Fw?)S-qN# z4yuPNZK?}#tDr$P+9D1+f7BRmH`w7mEezLRFTg8tdY^^Ki{V&DKx#%tHPGEHzQmi= zkFimQI0F_NG1Am}@aFV5aPFP?c$#+@SJPfE7C-32dvooLn~YgFMziW=PSF8%7806a z=L^B5t*N(_ycx?UQk7p8Sy%WC1A5n-eS5)Y5DINdxb9qwcHBQyCA3;m<@K3YD`L%1G0z$0 zt1|$ZS9`yb?Vp6x(#qd&Gxp&RI>vc}p<3W8sUA*ND?l@u`IOm!DR{$e%3SYIn zIA)yW6Cgl~?Y-TLN1AZ;+20B3WLcGWx)s>!!T!*-WtW)}ctjzz`cr_Fe_u<{ zvNq#p^Q|`P@7;J@Ho)%ZNG;x_B-~Z?pFnqU&$2+~c+4|2Sv~$Q5YOGLmdZcY14bu( ze{V$A0ORfbymc=xfM;e!NES^TT)Rv*Sa+ufQ{-ySPCpoj5rJEIED9-TWG5Ftlvsnx z96JGPw(VFa*wUav-ik&p(}OE4S;$wsIrZde1(t@4``x_t4$r@Bob+!gLla8*36;Qggu#PI<6pQ=Gu%T30$u_nFwf@R6%zBo&c_~^XRyqt42HKAG2}K`!U;D z|H0{>UGTAl$+0u83`0-Ul8Za%!_~K4iPzEzAT0f2l}ts?hXjIhmCji=!Ct&#!OYt`mIfS0CQmB;Y(`HtI4SAbiThU9e_S%D z#^@IPYgk^5`xBkB_^7h5z4!OuEYoo8l1s9aOH4z?xI4F&*^8l5H7r4(r5crV+eftv zWAUO;NX3(=Dh&T?>Z~`e07q5gYH4>Hp^)$L+m=ToX!xjRQ&Ia9e$pU!ZFlPh7wO;G z>|9l#LEBcrz}AGEb+-#$b^4(E2dS#=h&jI5{5y=xmFPfvsUoqn7Rld6Ec>+8<6Z@` zkG$?RklcP)O<}ncR0bGkvhG)-RczA4*HZ=HddsNmQ)GoMYFs?xGDXMs}IqETp~0d?;F|1hZFFqB_&N(f>NnOH!e6P}=^{K()6EM-P;1 zv)#{uuFK8WE%r8JGKoMQ^{WB+T9k0I!LkYz6P<<~h`8t{OF;n3b~ZlWf2rbpZZ5X6 z4H`L=S7LQMeboD7saQAHCL^rdjr&f#jyt=Zin4d=^x;oDnEYP;tTi4Jxpqpwzx^}xTvgiGq!OUco> zubKc?W~KR}r<0&baPh}suR5%CkTiNuk`BM}p2v5~siTGwhg0CcB<%ipy+Pqm41Ntu zeeDcGxaU)Uuw!W>@_cB&a#24H3xAw+e7V$*y^M*L-fS7DZgXfOfg>A(x!A}UwIZ;G zdy@KTR3hdaEnjZ9-U!C}@1|WT34EO)tR*0XTL8BJP> zDp8a>V(siw0X*01!kvGU;6Lv!(!ySflCLwm!^g7V^wo6LPIQ8Zb4MyC!U%Yt+;x92 zS2Er=NUpoa+JpifXBBd&8_@j01MOSv(db<5FwwLU4Rg8V=KG)2<7Bvm+20%0SgYG# z3_*ddAm73g)Pamcp=KUJL)r|&>*`v<~CgDuv;iPo2Eabn%Axte; zj3jw9nG@?@@Ur1l%ntWBF8#@`l)TUfugD6n8a@lgww!wJXB0UQ#939Ol^BbA-k4uM zn4JuC3~&F@8Yf^6Q^q;18>z_ayJXblmkmlA?WcH5Vli#{$e*!Mgm7hxYzBn_2wztz00_!==!g-zVFh zpg>md-<5;K_?DYf;QUSunmow$SncbD7s-F?D1Ifv^(TUhMnN^esX?7;uv!W+%HZ9> zg0N0ie_evS1FpQiZJTqMfF=Q(0yjp!V99fmj707fq~^3R^cQV~(B|2Y{iI!>B64Sy z{CO2>F+Y8lb-WqWe;ex`KV1WRm*kEJ1oWb|y1s`DonSy= zz#_;{iy1l33LHY-LTde0Lu#%ZeB>Y}eA74>Dy6f%m>q(14@jhk61+ZJUkw~YJh32*NPey{qfXw6YxWk@EB<=IwzUY*PX9xtnbGKqq z;@+t4o^Q=~_j1$Y=dWb&>5*5d6YT|H>_T#KVC)NAlxKcDXh}d0v2%&1YgaITd__;~ zd>yBN>Q;M4c~zWlxcPidEw^_pd)^@Wz5ug~)^SZ>RbDPk6V`sO}wkECE!>NgRkr5tn* zw5a`>K+HpN1{ns^A=q8)F{J7@h=&%3$}`1AP(GIGwZu{qcvW>UJ=d6mCtI21zg4PH z-|?VOx8)38GbWdKM8pLxLM=RE?#Ym->KYaKJ|Cx+J9N5zmkgUFr z)c1nh!ME=Fc!pu%O?ltU(SAfKg6iv}78GS>I3?`X4qPrFf3`|0gq~^CcnFyaW@+%EzJm17lMGE&Gm!fX9DiuK1Y(#H;82$gMA(nF<7O} zD#DfG32a9DxlOqW(XTg`^aG&^J8vuIT&c~%yHv|B7<)RfsLk#2o1R`c>rVgAXloeX zDBkBdI9`htsxHM;^s#tnh49t%ZULHH>5#v(w+q^5J5~qI7Qv!GDh02w4ibtVEe&;5 zAhVIzP{SLHEC+?FHVBZ)(HvD}r2(w4?|Q-~0aKvU3n@TgJM}`$obx zjcNf$U1*sYQw*?sN!>`9+jV67d=E4fGl!1qhjg{Fhs6hpVe*(4N{rNL>@#V z#V;9>lh#eZHt#i97TAY7+jX40Pov@QubkjPg+KIul?*m zp|qSPT>*a>`BMD&;YbfEpFc5`#@L0%K@yr1S8Lux>m{NaAS z=`m2;e$v*#>I+7)iM%Ec$ANp4RQJJJJ&L=2{q~(=0*3`+Zg?Ab;>^?E-_A@|!Mu$~ zcu~;`e(F$0H{!PZ_%U+h{ND&*k$7>BSvV9QTrS%~x)6uGqMXsAoK<)z>yP_vbv6j` zvDy`%FM=JyD+*89d^nPLZ2j4*Mg;bU>#v1N@H$^8=XZ}xWZ-u^ek`#b90M2<^HT|M zfP|!F#Ml{vwkVzmhz4VU$)e|P-X0Xm^YZ03j=+r;X%!8FG?d6aN5y^Y3w|!vQE&KD z4y@n!y1A9|(Eq0{{YnIJ-hv+_D;{gX_GK29BSvlTVZxTgizyoH@5|?l&%6Vi=_`=ygGV_67vx&T!V=(8fGN|W| zEavj^14XWso)cm*pwC*mDi&1<_MBQLyXdMwKKAw#>z+E`O5UG$tT`3zv)0}FNxH#! zUA%JY(=dQqO7d;%QDma-aO#K)$BHfLoHq?6kYu90$aACy%_Q^_B^K)-T|AboB`ph$ zU78(wjN&1@xM8gKOg$9e6TAJ*gm`{t=s2A7Pl1EAG>P8)xhT6wWlN2u6d#Lt4a}9_ zgMdp%{O@!&0>$F7)8gkMK=MJpYHoNm97wu#r0{bqwrko~|IROg>AO-YZ0!Ze93R9s z{%!yyb;oB+8}os~VdGnw$`YhDwudg(dqJ#hV(9443G_4{U^qn5gKNV&U$sI@QJS=S z*7i{rnl7sJB>588%_To}5$$9oee*>+cB>xm5j4JAlnz2fJ3FJ$b{ES2deCx+u@I7v z(1@QHLuf2%r)loU#uo=^*pd$q;JRbMQZ2nR>QvJ0E9Gql7Z#nOZGmC%;)+Rn7GMaT z3CHbsSc<@a?6IRZZ#Ot8-)XSs@5H*9=QMxHi_w9v4@8eN!dtStXU{rRW1Fqbj+JCJ z9*eDWFZ>n*$?8RsUfzws+5-If{Gp?35yaP z-%K$!KNO@EVa&nxbsCamcM7rMT+4EfbrWJJbqxB}Kt!P4ffCbLa2*qsRBMa|{STFg zoNs@DsD%{iiwsR5%a5nXy*7fqw8<50P>2yZc z9K27oE4`IE44-0DS_iWNfRXFZ^%CwHWOSJjq_9qbmAMeAlSG}PXxt%WiqsGM1RTYO zqK6>p%hQ&RYL*xs_Fm!2YA^`DX!pA7&6*(hq5qlW4fbss1a;Dr83X(H$s%HcJWi0 z0hF$sXgHHU1G0p)1Bp)xa8HNt%}@$Iq-@%njz8UnsTKmKC}z4aGim3(mvRjBCE7^J ze(QsuV}7Tz#R}lQd`_xK*A)I{rL?X;8)`|b>$%E}`E-Rm!#eTBJj)3&d^L@XbCcArB z-H@SJ%LW_Yb?I+J{(Xa7BiA)oEE2c9nkhg%J^gV&7w=+;G8WwJxSq(Zmc1a#FVYog0EzoeJ1}|AqezNHw zgU&k{f5b+jp@AZH*W_+3`pX;78JR|6YX6O1N#10%2@X6W{c8#rYx&Zn$l}rRwx!`S z(gv8<UOXwZT#(O-1 ze)ysZ&b_#w$9A+BXqO}IGdP9f@(a?2BEe`}?3K{BD9M3aXQGXQ4Z=ZW|8=8Fjd{SV z_~=^EbSo$t@SVNd5e@?uK_``GgD|c76~#%95~VHwV!h4~MQ!bf8h*xKS;i2igql1zbAqjHM~v8TXv((Qb@2V`D8An=Wf^ zG#wv+{^m=$RpiaY{GPG+BccaweohKh%=8iS$njQ%z-e&)>{G3wnvSA`G7*Em0gT)G zSBkya9cV9JG6~?ji$`9NF}%+v*5M0mwzp_w;Dev^xwp#0(9ZBabCRzc=WivieV^^a zcckp3b!x>Rpw8svITeRqpHknQmr8}>RXX~FemOzlM)l3)fE3 z_7QayJDs&C0zCDNSFIRCxJKu1U9j2#luFHhM{mx8o=~}f-6Ido7tss*6hi!e9TfG| zS*>99x%St})hy(iJ+o0=nhMMQK{BSS1JF43X60~f8MgHw%v`c4M{c^Fst32bf#g{! z+1TA`ERbaBjUd+ZK50UZDmg@+-LrnW{ZB45XsHRm*6T$L4O51uD`gPR>%2|KZUirZ z@RF+h0L-_|5)(a@h3t!4)KQZ;AmT{J{p{i(+~s(0^Jjbr*q$WLD7cRyfiwHD=Iu^= z&wh(GnJ^1(-g|Bf1r8#=53S{TT^`KE;pl939hkLFy?C-Q0;ay-nz`H&d-w@&UF)OZ zpi%%;_{Rp2)3~L+@F@&23RCMP`bMO@+bgL9lstk|xqX)LO~_H#XMbzKfs;C=_I4p?wo0kKqq>hA3T)Jj zr}D6EP~)BADtd}gEdOYOm{-B)!+<#No;0Rc zvRW`jae?X~R%i2wE|D-+VDXu3HqO5vPMgQ@eEZs_yyij68D!0g>^v3ieo2yiy} zqG{KF3W_bd6e(ZetvXxlzA+*n$hqrIz3v2T@BGrJt2=Qq`9q(cc{SGkIr^(|tr0%E z^Gg@7SHY^jbHe^i0z7YAI8c131n7BgdVT6H1Jixe3?^p&aCZ3Yu=(9wAmjdaG~BWQ zO}FuCW}X&D<3=m3S|Jx2JZqtBA8v@lS*9P`NdcqS`l0ihtZF_eH$CL) zE6f4kq655ptu(=SQZzcK`8`T+QJt+$X@y_}Pn6g1J;O(71GKd2uPYWAR-MPJ}GN6#>a8 zEj$^CJT!x1eCmm^Eac=LyPOl22kyqohCc0CK=R1T@btb8?5W&b`K(fawt}H|9TG}# zH`eBy-|Q4nwwB9K>J7ni0b8WJNB{&m6WS}!`$A_`+~u~MBpfA^D-@(6pp!Q3@w+5w5cLvm09d2LOCvq_7K# z`<|)6)XyjLn>LGaR-sL2ExZq$_E23+u%ARqCa?3ff<&IPO3H@uY6Uzu^S4*|XO1e7 z(IdxCbb#*GkaQbyzkG7^%v(|kf>9Bx6JtJ^@O)m)c-b`rRi3;5m})46m*2DVrxzL_ zi6Js-&$UblydL$-wXh2uPu4tJWB&{?@!D-PuZOTjM>?Uzl9;y>=fjF!t59=;rBEv+%v)Z|!NRKFsMiO&KB9U)rkMLZgMHV6V>cDkQKA-T>-r{E10009habvg=ci->eC$+;B?ug+)O(MTD9nO%XLQ~i_)Ql_f01< zq*CfF9c#mE<%p%Q@@%9y^?1bfNfFG`*BJ1cjbP%J1J=K5>XF{V)5NSG8pg<*FSK-5 z!eEZ#07rftMuF@@GU`HTa*IgO$_N1K>CPYL{lc)r`#4GFha%M4iFBEXiNWjaqbB9j zdDwSex%WVH1af)!EYUw|M18el)2l2k$QiKF**=_wJ6Aq4oTeVe-@a9c9c8Pa&}!<( zrcpA~WqWsymDl3c0~$N;LbEZ2!Z=~T>=WeivXe|Q=fk9MWi0bZ4m!;Xc!W1(!Is4q zr`Y~VaNy_f>prxCzVH58@NBeUn^~2&3|}R3-aJKSbSx1^LYPGcXoq2SDgE05jXhd- zY2Q9HoR16+Rvj+&Rj{#MbXbHw8~N4E9@%rnq0$A{gLgj@^~alCju{b2FqQv1{OF@l zNO9a1{}kyB!V*1q<_Fu*k!xd65#r&#?(SpZOrn41V1=HnQUSiXYO}ciPbcBjB@vvBHaU|Q>|PN)i=@O$BtjvUJ(#U62Wz#(&~Vy|B~oG#(|*uxtP{uxpK z;`}S2`ZN!Z%X2BbFSN-VV&n@=Vr}Vv;~Fsfblu}JlR9jXHECnn&cem~;ZV-$0<0dj zA-sJy0w?6M?n&v@V7RM{iNHb)x?4<&UKXpzM+2juHDCfAbI%05b?HW?n2`{59$%c_ zXII6l(u#W3E60nm0Mwq@9ix}cM*|T98SeARsP1dwr?^{!n&x8?f%nU>gz*crmRK>& zve#8HUFd~4+dV}q#srYLs6hJmYzNXUd%6rO&cUwq)sah+sVMch>{TYwhjBBjd)+KE z4z#c6EJnC@fyTXP^DiM4Fe+~S@lIA98c@z}#Bi6QUYLzk|GRQ18BLJ$EzpME+Dk)H z2Xo=TyOb7V^HeU%0HrUJYgg(tPM&go={}j#$t5sol(<-YS8?YORgJ`j%z7a_d;Z2pt$HG!<`r1 zI6Qn@WhW*aDBiMVX%N^@|dw;hf4m9t#8&Ba&C!pEsHs$erZ`*Ux7DeBZL za}};wfWOs8NqwUJH1%fT_Ruw=&!)X{lfHZc)Dw$lV>T*K_PRkc^^IEe8k{*b#~Fbm z?mwK2byFcx_9Ho;NebNg>Baqt$ajfxs8cqYB}2m&@5HQm12R<&t&`JcLy`T?Y+_pt zI=#A-VkuURbP_K$CR9r>_ixv$6_0r=n(aOFY@`Y;{kit-?v%qhW^+%cDt`#oHV>1u zX~YMlw%2uEw4m6fy+!t2-N0|iw~+f0=Z0#S6MiMN`&zB+}9oW_Ryb? z^utrw7ckI9Mbe5{lWmPNk-g9~$9QCjWCk9+8xZCzY=vK6zv|eWNP*6u7JDC>w?e?% z-K#ZZtzZyDb|aCq8aH>mlkc*&;;(Me3pLBcb^gTt)72L>@VxNS-PAkTAk#!botL`` zDYHT{vxgCH+2}6?vzCJOvU28W*&cX8#jt$qVG|M04Z7xi2m>)rW3iCGg)kAVOOrv@ zjL#2ioxGse1m&u?Nor)fv0&}V4IQHYOGy!FthxN~?L@NR+OJj^Z9Z`3a$zj|C^63+ zb#uj52KRFgoyEAFIC05Li~vk;)ic~a5J2EWi}OK_0IYp}rkLSbJ%*?`=%?@RN0oZM z6}4|gBh*WI<(FNlO!vsb0}y^Tf_7DI_tuWCGc zWxwTv7nQj4TvKdrJQdt4hLXb_I`F;bgZ0eoEL61*l&nZ;9!258L znvz;9E{&7lCwDHv#eQHo&C$cQRod@)2b!rzX|IU+HEATqoSL)cD2z7iJr^Wd@uqsl> ziR*h8=FE2JW>bY?WUXqTy+aN#4XR&1k=YKe`(ZfV|Xzl7w%Z!>2HtjfoqLS+dE=? zI6PzLs4ATf{;kO+zlw{X_b00qNl+F(jU!Nx$3{Y~nk?@JTg0pH#lPKL^g@Fh?2DIv zRD*8v@jg3_cbF!n;l1*-9rD}k<7F<@;p?S4Rz;avD7-I{ub1dgB#-P;+%@S(!`EG}4zuiShmzCi0q225F z7wbRc@sU8P9l^R5?=3*nt=AKfJJ4t#$diX#{Tot5F$$0NU#qh?nTI8b*B-RRCICx+ zz>AW|N;KK0;`4ng5SJd;ra7{Q!|%C0i^47w;Bk1hut1^&5`0^IsfqR8W9h__(dhJmK~Cv_L(^RUh|X7;ju3ht-8qcc5U z0W7vhOH=23aoc-_c?sj}E+F0s$EdgAv?yI^$x`tbqx$Nbl3Q*Tc)9TpyBH}uIAL-fDNL)V= zm4zk6(0lni-%q{{6mfsaJI<32Eh$<%7widG-&X-1{r`df(14_ObWBw4|;_%6OXyG6wMZ7z~&yA=3`DErCi>!C=bZ1nfv5FDXibtH`p zhdEj6LB+-_aH8fYcu2ey{MQpZxAqjH7#?^9 z@513$%kw!w5rn)|mvY}tqHp!Vx|A|?I(S73RqC^p!0labzn7GyX#DjP&jQgGv_UU# z^QbHbg?HSqGp$zPVCpgB>pHP8CO=t7?^OvkQ@YOmX*E#$#A=2^%@3X16XBjM(YI{f zd)F+m9Jm;Z&5CAg;rnG_9-+!~_$QP!M7mpwKGWv(2TwJ_^o`pct&jhlC+15?#GF-aO*A-tj0Q`SeN7Vak zVK;#-%u^r>$V#iIwB)1EL!01crI>~PWVo|6!W*FUD$`T>tGSq1=)qv`*#|Wy=MTF* zpG0oy1an@ZFLjouDwv}63x*7~Jv5ui0fouAx$6ht;7uh({x}g|7%w`zQ|?57KTQu= z1=2o%f6-p!G_g7qec7{D`FaNA{yNrfc_JOn3Lo;>7@A-aZ@2l%W*2rP9I6qU$wi+2 zV=mFed@ojd=HU#sLVXj@IAw&xXD!HoWB-fYbMHSEuR{@yg=J4^sYa zR9`9b+35>~Q?2##jn!?y{in?_cd7=Q7UJ5cDZ0^ysdy#&zzAM#l6&SN+zRv@kyHhr z{o%Ib&(wnR#Yo0--T#AXEs@_~ZlTY|<2-aT{C{)v>>*))R-!k73@;^4jFdRN6X-;k*3TaxN0eCeE4$3_8K=_u-e?%=M5*nw#k)o&?C~ z$$war$Sb^xB$Zj{zyVs@A1f3Ma74U;`XyC9{`{06L(krUWI_p_jy~-K{mUODS6GU1 zu=?on+h^3^#|^Vau0w-hmCJJ2jxQ6ZxtCZ@3$??ec;op_;<-tml6J6wSZ^NUxN=7< zh?wUZP-C{W3Mj`)UTeQE1107m zt4ldm)HVVmH_X3eBy_>%gYl?n(_D=GNv~f)#Pdw^g6;~;M7||SWT;H02mSxtts#pi z>Y`!b!qwRZ5AGeKGs>yL43d_48oPRoyLOcKKnnp93pRavLKFbr8Yc$zDHJ|%9B9e>l|f}-xzR@ zNvaNC_IEAbRBXhD^o-X(pBqNgA7^A2)=NRRwfoNQwO(XA#ZdV)Y!W}{j9k5Ts1=Qm zZ#|=&iG#o!yd9*(adX9V141f_!M|$u)#&R^e0@3N*>fTfZ-6|N3f&2yZJ5rkW=MdZ zm?#^ET!gvPuRKmYgGA`joW{{h0J#lQ+ z1Jg)>RD5$BKa9HEZ*&fX^}qU(%5K%Tx@4?no1O%TYzrrUjfB9a&Ka2~_0RC8k&STk z;smbFh)dc+`#YMG z_-o+Y*OY>KaQ-)2Lb%3>VCvKY`F)+y9*&+Z(-CR zvIh^|=<;V(`8{We(7+MZOP@rSlw zW}}o(bMfq319iusdKhKv+Px@2)T3kUy6D}Kz)kbbuM0t)aErz=qTVAHBa2;ry1A90 zDrFp_HqoCXMMpR!P!bFm_FpRya<9Sj2C0+kYl&cf#F)X*s1wafpETF`)L=@dqx%Sd zJ;c0dk6`>44@Vu!Kh2tjBh~HidpdZU0R#0L^;zFxyAJu&6bfI|6nNRK@x1~D2&A!T z)d@Iu`29!Qk~;LX=>9kJC>LG58fY6_y5XR_^1^GrIruYM+^SC)g0BW&YVSo8eYL;D zPZnMI0(VVT_(cCzA;msh7oVsctX^A9a4V`phHA?vp`tZ-oa3@cJ9Qee-z2@_&QJ+2 zlQ9d)IBGT#&L^}iy);iWgvij$ zIHHcks=5Cr$pnfjFui!u)D8x74{n@V3WUek&Ir+0RG~z$OHE7{22xoMrdJd}R?;U7|JA&86>z;0pq*=@|cAe^iUF z>&dQ24xenP=EhPIC-q+1PNO{ z7;}=6j#KvGxvWp=nX(mV;~ZYhIg^8r=U(S>6Mf5DsvfNKT0`J?&4D8xLFX$--s2vbV7np*tD{MeHDT0SthXOFQE{R2N{ zUPX}A4}!#JyWNzrF=$Nra0Rh<+XWJufC- z#UOJF|82jg)v#k}d}{BXQ6m56Ehci2fHNa|>y%9<;PKPKLwqNkVfir6){~WW*ne|- zurijYo2FfEzJ0U>vR2Pq)xYdUeZyY|?w>7x!hfDuv`PGyMNBRP?Ulc zflgI@kjTSGj0E-HuZMDhWN$C)eAL{vr8r{0fbWl1ckWKjV>8|INZmv$DqnUHm=BzS zEsvPLUA~nV`Hoj5Qi*`mJF-(AT~$CYT5C|-77xM6^E(BHr(i_A^k{!_D362rrJ? zy`a;u#g()a0oCPikN6PpYxq6Y_x14ag}0W9)Z*qHV8JXaR@YMve+H@Pf9?sf@^1ypAsBh)lkBER%AnEVNEMew4sM^=rfm1Og3Pv; zhAO(PFnuhXxx2y!U)kLKl<8gzqOaJ=`CY?MG&7c?lO-2@Ssew;e>oq&hm9msLpo`L53-^yAo1JQ6&JaCqH->{M9w}$vgCpNI((x4*q zgJ^wWhnH4`P}4N@wcl+Vj;lyGC>fQ&g{y~Y_8cF951034?z1UDb3dmhhG%uac0<1W z_v0#HH!%9##or2F$**!~#CPKMhjL{z_GZkl3yQYmC*D(zzSTYan&=-Vk=f45>w$e{ z3#XKATHrMAq>)g-Ja}C&{@1e{f^7dX4{4eALapWO^xk`2sPvP6ESRnoOFUE?o4WJC zSu^zF4dxitx&#I&;ftHDo>8m)`ASrK|V9j zX56|s?0rM33`K_z(QJ3-;>Cyamb7)XKwB9ZHXzafYWz3fr7Q-3M1cmSK6JwU`(Bw^ z?XB=L;rkJa8sdHPv#oJq!E0zCK{Oq-WMdU&P%`&%;(2^RXko81=1m= zM*;p>aIfnmhw|4hpfgC~8eSU1-PEX8AK5BkYEtD6{mXnje>R|JytW0Jv*)K$*NFc1 z0A+qLjWOUD)i;_m=>|CmuJZw(OYy(abe{2C{@)+ZDk@ZbHOxYUL?k0lWE3h%8Ksbt zN|9t%X7()Ed++(NIriRr&$4H!Xy||aZv1b$)5GVJ_jR53InUSgv@Twkvli$?wMX{j z9*2ui)<^6VIbS6esSL;W?E zCM=Z-rTWn{j6&KLwja+mV{D`D9aX+^bbG$`&z+y=9SIJnlw7ox zUaT9f{&jz;KU4z;vlpEAWE1n6q-vedKhn_odjInu1m~a`rSrpTU;-B|+;VBXoq&4m z2dTo@gYaRsR6^15M)VW~r%szmto}@8$aVN7y6KsS@1~yu0r~2l1>Z=7DVpmurRnJC z5XJx3JOG*W)cMK@?zz3lHfuAu0RCGtjTdAf_#XBQ+$<~vDL;KJil1e8VzV%6*GeuZ zQ<^;=Ib{n6H6-2*6LazBBc4W%FIsVI=7z$=*;MR{YHh^+DscdJvJzAgQg@Labn3WVmoH3W~Mu&wf9L;|Gq<>vbcf?zL^4P0E3UZh27n zm5ix;e`MQzszD=KYfo8q9tfodT(l7H$9$2%=;quptWUn4JYF||8@4ApTV8~EkE>%a(i7csXL zV}6)VUwn&P_Cl~8Qf+pXb67guH+p))epS@Lp_n{WOHbt;$v?s(GFGY zk!$BJx5I0ZNO6bxpyj=fAS`QsFy1x+pK(@`nopNt zvB6`@@a-oU;bT5H8lF>uj`1)x1#<1ETLQKRY)e~ z&-vai6!#qoSoYe^!S$2V2aHc=VYakko~LsslC}*ms5<7Mvrf<4i}ew_KPz3QquGMb z4^NIJQl$fVN*|u?n$n)nmD?#FkJ<0|v(w&k1pr;V0fkEndrHTwBchav*nv z-~#e@x7Q`WTfyH4dHz-5pI6yYKHdn2ug5i~Nkst{?>Ab<4lmsBxN5xpu@VK%uh>3I zAvn38|4GpPs7AiC8QJQu=B6S^X&0Dq|Ms(oi8X0XtXu0L*0mZTAI`iXUR_Nk(M~< zjCpv3ywCqmMmvtz@%d~{`@$Em)W_0#pP;nDcem1ZIdo{fA1k@TsY{xubk64qP&9%j9au8yymN*v{GE^;hKEnO8po zooL_Y+3|5AFVEcWls1n?-}#$2U8+OD>E|l{*{%UuOrhhfWG5Ur_?N4jJ`QZ-Uy~WF ze!~5J*Di%Q5>E|};*WDpA!tCM>v!v3E1nsqJu-Q!AEkKTop~ObhIUevO|yzg_#(*3 z+dhrR{my(d(bKL2F4o-pc^W+sP`bq(q}GU5!BXUZYzA=G=4sp~Y=mdyxo zxaDPuZzmzWDVwXUUj+(pu?VeOjuQE$j}Ki8NFbix+%Z>`f+_=H11xiSIC0(o0=Zx* zEblcIG9cy+C%13A#JR=5S?NCK6zgmlTAy7XT+PIV&|;Pg%>78VWN08_QH431Y{tw) zZb^H;sybH^g2$4;$q$P`C{*M(_bnk8?QgGMHxOG#>A5taF!Nwof0H#eI-Q4St?%qs zjI4r%9oIuuBi+cyI?jG1!r4U`s)8Ca|~y|GF;zN!4ygezhgOD*U%h&z|Cs|J4=rG58QC-U7tMH}u^6~lpC0U3p| ze4u78YPtS69p$R;{5>O3iam1qlXQArAZFafG%Z?*iXA?q4c)PzIrP-UsG<@YbcRmu z+B=MMCT>IbD|%s1oa9t_u_LTyJh)yW(*k6}du{FZhv53H-jK!j(fH|&q(lu<6&jqH zNIbMbpw;LF$VdTc{+S=ckI8TP$LMW4Cg{q2PQd{jc#^?BmbK;v<*O7CnjK z&4pYM^T`;Iyu~x|#1}7>%!%jK^`pt@AfdC(en>0xA3eQIBaVO0Ag48Tz}MD)_pbTZ zqKM=A*lXRFXt>R(JpR5H6G)r)56qRLVgK|hH&KV25qolZCZ!p7(RY>Ee@*~6^s#@X zAK^Ci}n@=BB|5!^mpR@mARk$z~oRTylEz9ED@^#rFbj7x!*;gVRV`4 zzq(Xt)~CIzZ}thC?TtgHP6ZHi0wSnoQw@p6%qO{~lOWSFi({V1f6XMBOHtcIqY2-c z8%r#`@Vd;uxybeQ?)e!3#Jn-V<-r=7Ul$=KJy11kiPLrH`n$CTadV) zn7;*96nJ`WB;zsR)x^|}MsyW9AJ}m;4&=%{i3|F7LB7Eg@Bh-<@%BeExBRVK^vq!T z9V(v*Lm??;$#reWDn35>fZ$6`8{Hxgde#IPuCE?Sn^)q6RJkL%|JuNHo$lkEpMJPu z7BCR;-w7}~t*7*{Aqm}aexm9a34R~bzALy~0ZwLxojfD+_<=6FudZzj9F+D1^-{;f zNBfSm4|$q^Wn8T0pu#LD?WuOZpj84}`zFn#r<&2$A&ve&qCYH97rwGDqzgx#dp|xp z@d+j)%g7hwtKipBs?sBsIne7j!>8Lq0`s$P_fUl9;_$0Xx7#Jj7&|?ascTe^)I8U& z8xIx3D$|ox(c)-$Wb>aR^ETn(nZlJ-gGA^INqI>2jRZBkx3>-%#b7YaoBr!#h2SgOb3d%WMytL-N2#GCn_$^xwvl z4&XdH?0#aS5?qX(Z;6iN<2NC?Q}6bdVaD2u@fWMQ6zEpdXSA0k`WJbc`HwaoK_s?xkHmlBkgoUJ`I&b# zzG45BagCIP!uvJo^v(@p-{zL~Z~*bV%c2d7UduuK14Z>+lci9ezV74gSWC=VVxJJl z5LUN!23&5%p{u|;^YykGq&=gcL2FftmqY*fNcGpE3aQy+L7m8Xx-*7zUChFzoTm9v z=3L}Xb2Fxsibk1eCysA7yW#fVf%bjm1Xmz(Q=mkk9^6i?uO<5>!&S{k=hB~6VpdCl z&z^$_FAnLDNo5uQxrSQJ_v|X@u{SgwbdEqtV~XmyDR&&q__I&xgez`byWeVB>IH8p z=j$iki{O3j!}_S+AiVL<=!S~^2jHwqeXE{cgXjL$SzXcp2vA#_GpZPlDg85JtzLat zn&~q&rk9GG!e!%cgR+4pJL9D4O~Ny>5Srok{Vg14l77K@gy5(}$2lS{wBZd6HqD$v zow)A$k`m!*X{pf7 z)NF!AN4BAyol5xlsQP?{b{Q%*-ZZ9M^23Z^wV&2|qKW^{xG1utJ@D(ahz@L}VfJt3 zqW1JrY&Fs35fjRR$5U-Tr&`kROW$3+bCqRay=WYHG_M+;3AY8x$hYG|XR48~U4*CN z(Kcfb@$-{MkE|l4>v4-gllk4X2Iw%E9u8h^!9g}1N3HC92qo@n!)u6tt_jkf?VrJo zcNeH~))G;ULeW5MpDrp@Ftc2RV$ez|4ZctGojcS5_3}vmnCHgDI>W00$lO(*?O-;%(&qr_3aQAq%5g52-J)Qit0z&LXPBYdO!UA`g!jNeSY9Ain zFV9tq&zK7?-&hR8GC>PbtIRYU7R*xqm|qP?50r)_wa&uKnm+UJi*D4TJz~4}U>o>t zOnBXJOvlkTvNU2D<-k@}QG9H^3O3iW`o6Z-!Q8QVR9PTlO#R!~S3&t8qqCT;+Lj28 z>&gb!T8S_wX_`r+;BVW_Xsv%E>(riNaJG0LwY@xoyA4$@ZM+$RS;L@LMl!RoeyaW9$jA_Qv^_Ju zG!}y6XXy*$d*5UKch0RE$7#@({Bt`h&kyZIg0D*vb2t0gyH5&7ry)S)%pU=YDoips z+Bg~!hms7!HI>68EZ_fE>93wI@XrQ1UkNS3=gEDzCwB2O*eSfL=y+NukV>v#GG5&Vwg*T+gF^?7Jx@*zd=Vk|U%I^h~RT95DK z?!CV<{0xpT)y*Ys6v2*N_+Is{H_|2R^6%~JgaiXK-|WN) z=noRN;8}NqjIWCMblq-9mi16oj5{6Vx_xV7mHbAC1!#dPkyV#=*UV+$BI`meJjKtO8WDbkh9CZ-VrLLv7<;+29dS z_~bzfF(2pFAAM!n2YGz$Uzvisp^aX@PkL?+B1VmPx{tKLHk=#`bjg5;Z0~em@lv4S zK9gcv--S(1&#B6`+F?NU`_vw05`G!u)xR!Pj@L1aW167`GxBy#dgVAle!~(^)BR+a zX;|u59&Lktl}FvYiM;bjym}$$@kV%(n|7kpvl*(Dt@ht3jl!?e?}NzWD!@FLgN}W)82@--+J487>jMQanSAzh z_#b~X>3GCH%oK&b5f559wA>+f_h8FdnJvmYZnFq^B_Pd2!qy9c95}T+Mx_;52@3h_ zP7jHD_Qyxc%%Ay6km^Lik2tm}Fr>7~SbkIwl(Pn$4b?I5?P80>Vh;&}PjSh7K9G+q zPuyLW~k>t-T&Wv7S`eWaYHw<3nWy$3oYL0Yy>M3aZ)CbxDHA@lrqzZs!2 zbZj%1ycJFYbq@OlWyUZ(Fqg%6a)RJYUDbcG$i|?^OhBS*P6F6$MbHXHl>wF1?lkfW zH&pP7d)r--h!TqVyt9|f;E%vshHPjQuDaAzDgDWUNI2J)7&wKUtSu$qaw|dOs#xon zsbsADa^c=ok}Hn=%5OBkF#+8_+II8P`=X-aiKoAV^1l^;pT9XOb2+>zmZk`9tI`Osw^sd%klSduh;xojXtJkT53b}Sn*D)-TBu8OpEsz zz4bE}Upc(85n=BEd2i}STmF2Uc`|&>Gv62Pj*e)sca-8}5|iK+hi(Aco8lXh`N)%y zA++mxA$r6gPmyj_gGbDdDJq9D(Di9x(7Dn={I8^3t)uWcewiy94!tpsscM&j{(pJI z1*XfN-0MN`$kW(CNs&OywgJiYn427GO_ z(gcM_V48okbUmySm1bpI<^w-LXoJ;(m$pfGaVoF#hiN5tu=N#49gM`^L6S}fH8U}t z>)oRM#Yyy;KE`(ANDYQ~a^7(CNPu!>%G{$*2~TJJ&^Rq)1!jBIJX3gOgDs{4N7;J` zzHa~O>axPu$|PA7%vNoI#IA z%XA%Z?pKJIqiiC)r^dXyhb&+wRdXc7{yii;k8gjX)(8eFcWR)p1U3$4(I3?f!2%zp zj}VDEl>Wu&PVc8NZvW_7ASu`I6@!<{hDaQ8a>YTh0m-ewv(=Tr|~*XrM96I_+Z zLH4Ka&PKRdaKB(Qp5WrbCPS%;8?aQTiC@~n6=}!}8E*O);m_B{G+j?OW4TMVISs+p zU!Z&?M^he$;l~~+@dw9Zm!^C1gs?L_GnB({)pV%&U~*68Sq^6Pif_rr_JKjsfm zB7AIOy^}B;2{Ag!re8>T*c3~{Q&Ld_*R{?Ddlr;IbpyMy58=&hj1y(C_iDj?$5JdA z(uf@Ir(!qtv!%Fy{5v4l?OyM6fyf8y_#O0H^t4e+aPAE@w+f*Qq2#>=dHytY5j!}Y`e=HW|hXRijr z-4i>GN5m?jq)DQ$o*@(Tk`4y3yBI^0L*YoY)i@a6_NKD?RDdM8?C{dlACW9P?o*|H z09tM@$rp@QW0lxDm8T+I=(A*y+fDSxB>2vF@*B2bM_B5M+rx#x>Y{Y`1$M(ia7~}0 zt|u^bv&w&;>4exDPa@?0btybT+@zj1&g~`xelpa zc*24{+@0X?7VD;NIm|fYOZC@F{7;G8Cj8h(RT6?cG^x|9XVPGh-Dq8IBNzPlebOt? zsDy&XDau;oIhbSI9mStUcn{7vbuBl<UWTv} zT+Faw+XXfam*3IEx})lQN~OJoXO`^rJ08igB)qV6&&1t@c+Pr_tDX+XMXhd`VDE)W zbo%|E!sC4gO6=jb|6*Y%ELwPV(zQOrUgz)#ZFU^h^RB|qp40W?+48CI>|95EXmCxj1L#j5bfPWU09Nl&K`(`b{H~jqbfZ+B1m>EV5O+;hj&o?Q*x?<6CiR)Ov zL<~mlK69^};K1lvY;$d{bs;l{gQn`6a$H*F?%9zeIM;=GVA$1yu3V0k&RWh$wekFZ z+YKTwlXb|TMk*VguFG{Y{*DCh#S3Q-QkMbUkMI92E{1}6yx#rMFCM6T{HsUDN*$iw zDSOH^T#QC_9JW+P-a<;?HO08qAoTnodZ~c8hq$FhJowI#h+E!EwD)$G!*AA&A$i&w zl;A0OYfYX5VkJTH(P~ZjQex`S1o1w}noZ$9bg>E^we#JSJxkQlyC3oC8=4@Wlgph) zM4$E92hpDwADR=nFw4)I``Urmda`DNz6+nV+y6C+e}IQKYtkH4q9N;wUfKt}EcE`q zm-nh+CF&f!t>7hH1=UM`|Ew3ihvbG$E86)wP<`gv!=m#BZOQZ}tmyJU{PYFmJmqS5 zdWr1&;&H+sH+_Yj%-sRbir(8Yl^=ws-744YP6ojAV(d)$APKvb6#hw-rXus(r!|!( zEg-mURU}UT3G55qt$m-A;KU^LTA64bu&TQopiUJ8{orap)Ju4qMN2s~ZJLOC()@Cr zS_M2gS(C{4;w6^CQ`+U0B3$a*7vG?ngxsfEtY>qb;KR7jn1)UnaQ9gH_C_Z{j)s-b z?+0Bt?aD;`xFrFj@Y92EK##=5`|{YD)CNI|7yZLC*Z$Tp_Y-Fh64RE$pKeMSjDfu<^0wML|)2p)0}$;|HW&m z2)VrjMcpm6lCR0wIu&-_@NhO(-_({b{D~Ov`;tB+xg2h_yuYz_(;1@~j?ACnj)6kz z)V#MPwMa3lO4rEW53-sokYvH;06N>tCT}Z|B+2|dRtet+OPcB%%|rl-Oxr2RoyL3Ns3sUAGX_=~Og7U2i! zZsR?L`M6IHzbJ2IgQF}DW%}PdtVTzb>|L(1<0${HxyxFaqh1`h?Z=QQyfgbrL zJ}%=FT%Src@+fM7-PNoYa);AVEhhRYp46y&roNC_;k{=RZ2G12k9KX^JDwa}| z+vaeVi0H)Ce0BPY!UEhaASn>KK|)h7cHFbK8SaTD{8R{ugk5xgN=~FYbYB~vc;TFZ zRjSp7Qa%o_GNXTzn=Kpk6Q*51$yEaXL!l#d83b?ig%l0Bw_GiR-4~UK z>IeW03HH8TRpR3N^xgf9uzED&|MNbgssROgcBS)cj^m;I)$7NlDzILfAyJ$;79Pcv z@shcf!9%yA0ZsWn3=UDZJ@vN_3u(3rhGtVyp}|a@FQ5}{o3M1K7nP#eiS53iV>Nh2 z{q0FDg%Z#)|4-0>XBZy_^RGEcSYsvi$=hoGTJQzcQri!<4v5kdY+0h{MKa$2U*&Jv zP^#FNlS$DFi#F{4sLLy%QeZNS+9`_Q0M0-9UPy3luATon+jH?prbpUJ!X$FniV%uJ zSG?PCEa5qQHJU4k+?wDm!jKAA@4B*bpgkwHoc% z*D9_zRYKq~?S%2NLg2S~CUHy2ANyj{l$pIJXUBrCUl1+ZpiOxBl0aKHSvj@-mNla$Hk8YO=nfAX%ZE7Yq)cQ~6C z_t`SIYk$2~LZt?<5*2;nyUvn57D0O7#Fy7 zYh7CY-e=_NKdeFbw+p>C%$@rNJ@ByOlXo2!#C*(KdC7^`SE87k$a5&d1#8UcUW*c=m=u|9`jDoD==v1;zAnH@RGdS5BelKPM1-4)TTG zd`pMR9Q@xEgO4f4sZ%M-7fB zrI{V<3I@q`Co>b(IKneKC*nX}jo+S-9R8LTVBMt}K3ks*yf&9O?nZc@1%z!pZ&IaV z2G_3_yacc3TGaY(!!aK(%Pr))69KF*QiT%=|5f1ugF>kWx(OKI^E+xqaMXW?-RWG` zgW=*x&h1Y!G0@0*xXS5m6>Mp0TrEj1Lf!qhjmSMUAezF^?pk^up4yc~Yn&elw1qCL z-Ph`{;+zXw;OqQQ-gcp(PlBwjz}_Ee5t|CT%&mUv_ec> z(YUeuV=_{Fb@E&_al+)sN8UM}&%x2BIYF_`2&o%W@yuR4V@w?{9U|#>OQtaLN0C?A zcr2zJdGYI(W;g`;-<(Y0^u-%r9rdTNj)y65symu08iV*&zM^V zpqiJwyZx_Bd^~hBd^T(tPVBVX7P5~M^LBk+(x(=rF3?D$aPTMgn8b&&7G>iHO1+h7 z{XVqjk(h}5m4thxH-C*4CZfsHO)ig?bUendwoPu(hz9BcVJBvKFgMk2(C+IbeaZw<*7vtzO9&n-XbD}uF1@6VR@y%0>lvD8g`Zw`v* zW2V%7!76{-b0xDEwQU?uX6pLj;(NE)QHe>klE|52wx|QT;L0!5jfI%9E>d`CAr0wu zV{|{H_M`QG{e7*5ZD_XOa^>Pk3Cu6o7W^C=gPe-nCv4AmBjrEc_7(p&kjp-DEPX8% zU+gpLkU3b7oSAC!rq6TGW0CCq@tFkN&fX<9OO*mEAwe7DM4cjTU23fxQHiMyxsmv>O1DZ$*`pnw#k3}cDLV6Ce8j()w&E$Y(?TrCO+Dbfg&psezwE!4=_6SW7zVT_- zY!!)rrO@V@2KI9#8dMQ|jR-~=a1`^ze{{OvbU2D|`!l&Yl~)j6 zG76H@HO@oFPaoWDk~(oT@1A3d=M?$~vPl^0Bar$f_1guBex8W-akURgXdNW^GKTP# zYGvhckcvBDzstetMB60Lmigurbgv27_8$to;#P;+>`pEZz9gW+Hwmhck|-$Mx%iHD zy$m?Lgwi8*h@4~aIf?H7DnYg`XGe{<1#GRDXiF>$VBa(|wcJuXRNw19XnHjq)`yr$ zG4Yi+EAx3Vn8=5{Yac%LRf72ZiLM8d`V*nVbVgv5$Wgq>Ad^u$T?V4}TDRsd^n$C! zR`DxcGB zMKgNG!^SLxr)=g-pzH1TaLZ4!C*ouj3RG9h854Q!P&bG2J$>o$^V!6Yo(zKD>)9pd zFXfDpyRSC9vmhbavgFq&j|iYlNe}+*=Li>Df9AiOtH3Z3p%^)VR1iD=G5-a5F<#xN zmVfzQF7SvubJ)8Kfj2D!1^e?clnF9l^gt`P{wwlP;&~nvsnz+9-HezM@17I?Ok~x2 zw#2r&Z&V^<|D!Ac$zrHVap7+pt_Gd=`5}8F^We0gG3~G~;VYRE-ut4g2D}xO6pk?l z<0Iw$N=r{j7`QQ~G}-O~eTyq~Rf>p4tPM|^oQlyaTGeQ*E)yqDQx3Y4 z0IX54zmr1jYf>&4l;5D?({q+@@)!R+Jdst$n|6N^cf*PG=H;P`pk3y_NBy$ z{_HKnFm?UHlb1fC`}>IZn&+Azw2%JZ>wpmOyJ(nl(4qkP-@1%BZe&11t!-d6@t!QM zqH=mj^d0uf`#)9?CH9_a-#<7mnFjkNH`vAn%<~Y15L~)MzJ8_gycqjEM z6H@akuYaJZ1RC})_B%ulS9M-*-9g6_eRvWSX|iizp!y7mjaI?Wg35~@hReXwF}IOU zZ3tg&N2aTNvxkPvvP+@nwRqc>PV)3jIkGX%GdP~U2poNjEQOs_;JeH0C=+`Re&vmA zk0<&PKlZynU@0rWGLyiM#;d*fZ>#N><%dSNcs{KAg>@3ab1BeK#Joh0PlubL?^d7; zu26JNRAQW7?_Mt_cPRH}O#8~n^1L;8r{3h`$^S~wjBUO@#UdZ0m8Bi481u1Qn5vJ;qyxtmCmwg&<{};Mji>G> zN`YtRQQM3BH0Zwn^CC%v@COK_eA&fS4ws}vnf|a5*Rpc5nyAWH_{PTEzmm}f3YU(J zy32HejBC*@@wImNdG(L9d|d=CG_!|9$<)I{n<>X9x;7|$d$4AL@L=|o?FiX&birco zd&v;pcTnQ8Uv2bnEtU+~Jmyn#z!%ejjmwPw06pgW1hu>17fJLmeRUm=BK9U09B`CSiE@DFT~fE@`@G@GK0V!jEn*(nCq;2zGXf}@OAm_)XME0_8;Yrcx!YdvASQ_aPO>@5zp3QH`IfiAz=^FE% zLl1~tc$Vrj8SxUJ=<1;T!(9foexJoh_=~We{f9_VKm)XnP&dbA)k46OXy~u37+k$m zH(=^Zi|0hv6CF+5EX?H(Pj4GtAWr~&L)b@~{<<6MT4(pr)0JWu z^R?J@ni_a%DMOhh+K%cGk=~mbEohmP(4yR23z_RN#bdNxm=Z=UHs?ls&jyxmez%DH zp@DFf%rRsUzV~6{VJck7bFyXX^j_HLZ6kuB^$ zSOdEvg8V;eD1q5AStX~0O0WoD-^={D6mODUWv2HcKA-tiu4i*QTw3{S7)9jTZ%`Mx zeu=5Uz&MFZ%PzIBdh|(GW>XvC?H{&jxRDE^3)XqZnQB3%GT@Yiz;oQRJ8)T=ZvcN@ zu*%w#m4uIL$|hf)h=Ay{P#p=?UcC0g-+|U886*SG$#!k_03(B_%Ed$JXq5bbNwC8n zed8JBUb9x=g8s?u!r5{-z^AuX?ch%ErtT7UA7cog`%bozS1wKdatA|H4s>xqs~39v*o+P=K+5B-}eDzAuq$d3Am=_7$IRP;X?!C_Djf%bkM zQwScUD@62bUwk33onH$NB+l(KJD-6Y9YM%cnbR{?(ttudqE`CPQ?M{|mduilIH#Lu zM_v)1U)A?ZV4y4sgfE{+9z5QGj8dth6@0I-HYb?oi+Bc*%GHE*WgBq#CF>mb?l>Sn zL^>m!TmUZ|q+SaV9y#$KiNRlL?HD78i^Bx})u`$+Y)zR5+*S>Dzuq2!W~$L4M#>&c z8Ic$&3L^Za$-H-zItgzfgRMD9hBzl4{pZYe)1jSXhR#%#*rRrB|FyQYQn)4FMIBPt zgh6&4k`84RV5l#bXSEOtdVGECElzp3Ya(Gkc~C9N>F-i?FwRD$ewI=}wQB6^P~5Y% zuN-Nv77A3ccY)EC)}=@%V(*J+j#p7pFS1c~4j;^`!*mzvpm2gO@#+ztbTa6}g{Nh+ zw&#fb9tZjk&5xJhu!#7T8uEP97-NgjU-Uyo$vEz5p?J{MwkNw=k_7%6se6tSGZp@A zZ|&>inYbO$Pt>OD_zj=K~ln=XqJeKH3J`uh{{*jOzvzVI~%l++m-raokW zF!kp!Yq=a;((cRp6xa+N1?#D=iGEQrIiKRUwm@(&{H307-UcuDhR^C8%OUU;e!~vw zWK5JhEapSpi;s$lY#fWLf<#-2;seTs`1fuJLr-uKe6o7!oRis&9jdRxuSJxDl9pP9 zYI7Z4*)Ff!Jy42f2ZRME-14yfQntyETp_-?qM3LqC?CtRt#ecK8=z-*z)c0leyr`& zWFQmjMxo3@3#Z5W@HZ-K^$|Wnx0}|nO9_KOyL+ptP?dy-;}wMVUP{Jr<s8A{L<){1US8C8tir?%Zk-|d*YL^7he0)|8#r#8t?;bnVF&9y-{a%O_|bc*-`XS@ z*+j3)%{E)3w_Jgx05MObpD!qGCwNF#o1Cne+&XM-Yvdc*sX(dRLuKyG3BX{@Q?-XP z8wJ+8heIykz-+3>XM6-#;1Ho6BBViW38ZlgSx4qF&}9<&oF|ux z12Odd*bzne$22F_ciz^hpAg7L6uyuC7^!CT&q65*?9kyHea zc;fl>n7mTBe3!=Xg=`4cue+~B$_C)DL#frmygxL%7i2bv5cBH3x~0qzf*Z4Qt?Y6y z#LIPNkM@e!A=S|!Rnecxp#Jaqg9533h|m;~*ow}FTdR-UTKWr+@9#I4iYg;KQldwv zW)}(X6q%!^Rs8Yyg@00Ns*Qvn)=@MlmEcM8x;>Ea3ARr>EPwJj3mjhNsP2hsgLeup z?~WUELwuEYow7wE`1{AyEl?)GtL{rG#~pHk^X(;5w#Vg!M=ik7Dmo1Z2hZZ@>c=klmnZ4TP1@2o`lMFVrm&!#;;(=p_{w%&v`u_xAn=?2eMD?Cl9GW)d| zgRLDeM|O1#qQYWSdv!_*{Be93DE~4Q7XoZVy!o5J=v}-}JmJaNKCMO-)q52irpMV@ z=vvV-YlhtVLN`#%Q|>zUIU6rsyk0-)6O5ae#h!{q7hqw*{I49o4CMa%oGX;82OXK) zhIfv2VcX0RvZ#ldz&+S_Cg5-aUXL)Pwdl)->zd?qGY{j?GUn0^X&b`hxE14u#5p|o zvLWpq*&MKQn5zIWr#7M+q2a$v)LXrcV;An&;m!3Av_ntEoFy(WliJjo_ zZ@7D&n%rN4`+D>8^ZXj|rdNHmC*hZr**~`5ezqJx7Ax8HC-|Z4t5fk?gx_o7)NV0f zjc{;{k>^RQFCq5Z%mxJWbwI|G3Oj`p71-eW=b5Zo3+|ce`DXU45NrIbjZCd-aeZfD z;3|6}R^8lx$G$ubY%hKNQy*6iYJ<<1qSym4^zZM}%Z~AIdF{Q*!j&*+&ZSF8I35c_ z3y&(@JoE5Iv%sqvjTXqc?RNQ+8qY<~+)hgj&m*3rbN* zu=`AS{9+3d{EdlZ&Au4e<&ZlsbEFNsK8|NN=;ou=HM2PVgkIR@+4A6*Dni=+V{2&Y zfYGFw(Z@HdA;76fy|p|S<6axy-y}ThO>+%l9Da$Id*5#{u`df0wI1s?6P(cjTmQoU zIA3COxJ1LP{uq#PD`{`{B>XWX7rqef92CBO(IVaO1G*Vs;0-wZ8j7wB*y)ADpzZec zvQgoE(&oJ{Yv|A1Z{D5kVTXKQ<+r`rZ4g;+Fb-*l+ZD6T>3 zp`QuHxg^-8p^_85UInCAXVuO!G=eNeTV_&y2;QafylnlU74;WNJ{q});pRd|PUe+V z__FwS)PiIMQ!`sFddG^8A?lWyr$ry=s{TvRcN{{u6TCZKA3hM?JlmuQg=*XsUGou=P|dx;q|!jKydjdxjp`l%j%8X^8r68ozlvb$brb? zVm21xULde6;xbHMh4k#X=L4+>zid*-MK*sD@(fuws9cOdrpF^6IEXyt>90}#m&gZk zUzn~1S?C}{hh_=pt@XjpF3aEF*{acL)9c+U@nBRg@yHLBiG?%I?#vv16$ zbZEh?BM#PYT^q66=#UQFuEI?+9sajLIdFRXT2+EuDLgzANB$t72~=-gIBz*X`0>)Y zx)yzE;DPeBcuU$ej8Ejy@sbaQo?n95H!t;pM3&JJjh~%(pQGKpSY!}}c_tmGPxhfo z+EF)A;Z_)bJa@fdF&LC9bsjums({`{iXGQaHe$|))hm0WD)9e30K0aJMz+vr1(}q!n zF9%YN-kr84@}>9tUncE*PsFmhJ!XfOysrq0SJX#KMXIpMAV;9isTcnfR%O~f-hw|#+CtHG z+339G5ZL>X$Qv^4J@7bt2&#J}4hd1s;#?ETb?2aM5XAx(Ob?%g1JE6;?YFz3`E#4ch+!EcLiEL4d5B>_&V8z~6!-cXm=#S=8+}WRvO_L0{ z=POMhv${-!m9_xd0?$6!uN)17l;)qda0j91<$l^Il84In4(7RbXawX`pSArWJp(4s z{ao#C4fu`W^_j6d#V0V#!6yHYamzixd_hB56kJX50e z^UE*5vENzB(T2Y8Ta^2}nO-z}P_NAYHQS3KALnHT%hKT>s~-0)!&0=JQ`u@T+l9}A z%XR?Cl?cjqrk7rRhJ0_`49$0T;0>h%0pjP{aPC~oyTI3hFmSGYb3q^l*kjJG?NhTu zKHA0zR=!xgwe@z=fIub4*)3DuOnHkWdXyxmNcfFk8F5D+2 zo#L9E0xzq$YX)xn;e{o>%!s)RoY?^1+AW0Roj|MgV_P*gl)lBzpej6WD=Wx!tq8B) zaD6N~?2QY{RqyYy5?%#E=Eo=6nP^^Py8kd;G#>WovPj=SJj;@EQddK|kWKD{b|6HbKS8yxLJc?ZZ!V?KqOsAcHzDvW<>kTjGMqTjn55t^a#h1<)iLj!4{AZH^29CC-Hg~9XdakHweFt zT`9gxSK(yHzslO-ddzpFnwq}U047|jHco^`^!d}2D1~4iXuDXAuS)0Os}Kpg``?=I zWrDcQs#yZEp$?;{lsZOD+?2e}U4^f=_C)S{eh+?q(0S*_?+6s%Gr|Lea`B3apOTSm z4Y1~%-e{^BfUQ?4Iu}H1AYPYEg`KsN^oc^*f}XpeoODyJ*iaL+w%g@DaH>H@=`~sF zuui1dsao6^RR%^zLo>Ugh_|*^Pk;A3;q$F*T4pN}@5`h7iz}45D6h*Tu_d_`T#rZI zKE@dZ^qZq2kA+*YCPDUi%|&SA&o_ zy9a+|6*_AE`!jHE1o&6vLq9kaVA1p%t!7m;lu(BCI&;TE${OeKcD)>=WcjaRtR)ol z_?*mtq%~k-Y^SB*&lVK3exw<&Hj1myT$q+M_*{0Qy z0cWdqg(C*qq5hUdp-yNM-lUgqlI;D8yj$5G${ZemAj*O0r9pC!4cOIs;Y%+n&^ygY zITpgh*(j~M?Dg=$hE(mr>_0|*cuL_b zqafh~_m0{+^^_L^tylp=%id@>%ZunldHv+a1+^HUO)26LjSi?$pXMK-0Y{4@=`G zpqF;=8RO3k*xl;oOGWY#PHS{k>ZfW!J#dBDpYU=2t9h?!ak30*X8l^OagbhcCX;?J zYa9rN@Hs7HR>D^~tH|SoP;)N!Nw`Uf;M7SL1 zj5ZxtGAqD+a7u}KF%hoW`Hl(*)T0E$*GX=kICQU_D@oxk#GY`8G0Edq;3=DK@vFWV z{`h8Po~RekZTEFwt35UI+NVDRA=AO;3^QisT`2u z*#?baH^2B<5RUOMx9|?dQQW;q8E!<*&Gzl({<#O2@%{b-+{v{qU5vPPD&ub}2C-1pfUta}#bIMqa%+la!@+Bc1CVw2;vEAV+S!ev$ zt8MHjQU?ZStbTe)#e=`F|@B9L3)vs6;>Rh=s%P>{FSa7 zFw;$CEuo(>9xHhkyx%$~3#(5bl`69#cHG2^KQN~~_Wnr1PelY>oi~gyr zua`jb*(V#e5+#Tuc`@(ThR~oY*>=^X8fU1~=d`5?ux*lgf=}NG9MYQuBiA#q(}{<& zva}1!$F`R*PiErt$dfw$pBu1j{PQSfat@^Jeo`1HIs*CCzi022R6$(r-fsSXrRZrk zQr^s(10|cBFL=Cju;J+M7426IXl)_S!AkPoDkjS}#G;8$a{Gahn4~ocu@?luf4nRr)1Ijatj{^><_d5*EOSoRsRY)P zc-h!VubU^VPdh!R60A6njunI@cxb*+nP)}9;{dSb#W*gTV)>KWPaTR zyHZ;BR4qS8)~VhqP7Mo;`TNwz*scc-7e88C4Iz2NYPB%-@+jC_f37o|h45R01`iY; zufds%E0am>!B7Eu%R+y9aMS5IeZXfYxa2JH*pZvem9z(?jiLw#x-eizi(C~fat*qt z{^~`OpF8ZMd0OxVjmm8I?RKoS9+sHrjmL?uOEd2&V{lt4?`WcF4vO&ZU*y#Z0;5*z znVI2AP)?23UvTh&L0TqzTPkJHe9l7gG_epm@+_b9O%i`dPFnEzy-Xa{HQe##_Gh>~ ztLUVY;*P`8TjOrt?g6))q?Te@U!<3+emf|Xhk?Blv&Bb?f!6TTwA%T0thgfO;89%$ zlUZT39I-XT%e40C=HC&_r}K5|NUI>+5N>Xlo)X-lqb8vn+k%h(II@hK=|yEe$MmWh zGS3rYc;h5T{8HaE)WQ`DA?ntf_G@;xAnt0?OR54t81kt zMTUugtNZPreHBf3>lhF9ewlaRW^u0GKQj()a9A9^@|}2G9yDIuut`LQQ@`Fh+;4^Y zpsayyU6t5>==dWVvc9Gj_?7#3Kgo%{>z1GvsemStCI6Xop^zG|x>eb006!XCNarwU z!Xu>xpL(fl@l)Q_!`(@q_&e>7!NtCKOudsbHSw$n2O7F~4`y`WCF>_P<12*sw}-}e z-%17qYX7^CNbQNQ-``>&%QcX368-r84)Jk2iZ(g(XTjBjI?u8q>f!SV?JGUp{TTRe zQ*v^-0c$V5K&~f>v3@^8uRdLF8Y?i_a8OXZ@nwPIe(xj)4T^l zj4pY!wS0hqlD9>#3W<^?Hu_kD%%Qbjn^i0cv7QxQ{<91KHgcBMU#WamD+wx9=?t6kTLcOFTpNuf}QT z46j6?vv@4G)9VWOC)bP-&xT?DU1jlVwpyUMS{o-^ItaV!Wt1g|hec=mK+D=}GITcA zcAt3WhJSe_b`2VrK%R`|vnIli4>lH~F(8nXkj3Hc5%(H!r14FHk?s`E*S)@1WZnyV zoEL=|810}sN3M8tss)@4mXd`QXF&N{8&`hH7%pxe1KO`KxFT;UNohWUiHF1F4l0n? zHvQ%C+dP$Umn)^){O16!Q@)$`I#Pvs<0%(*e#^%@H>6l@w@2cy@@C#F#XejJt28l? zNCrG88{{xphsW0S`Qx+_v2ZVXX9;Hly$Z{%A+a8K`1Q#??vLcqH8RP&I)`vQ62n&I zENWo3%R*YyzX&}43paW!J^_jPx19W#CPDnL!5;H(;g}Hc;xz?d20V>PG^OXPK<8@e zS!Tjl^VJ&+J3Cm26rFlZTdEpyI8m;9$LUTuGIP6ln!^WrA0&^QyO#p?KAQimDf56& z@#F7LnfCZ}^#XIXs6TEQ8?sgU?g~NDZL<;K?Z{9+!`xre2~xdp4GsHi;T>I>4u6^p z4CuvKgc-huYnoxOs@sWeb)z1A-4z&kO8?2e`!U$lVo6UkbQ4eR|JWgPrwjiC#-*C( zCPG43nN#36@sTes8W^fZqEF7T^ODyKK_92Mp6)KgxX!t`IdZ;TH}Ej8-cJ`k=dh_= z)o2CI|Foy1Mem~iaEOHQ-5T6I`&jB?b0=tzk9-h0mxnvzQ}*1P?FG3%J~pSd95Mc+ zsR$)q9b`%TlNy^I1fP`0Y13~KQPaFezN4TJ$9b%1u3t{Umcwb@JjZ=d<$$e*!K--S z8~Q^Vl^6@(C$u+HR%_vvg3zh}G=;ef_)ZU5i{2wdhCzfCxiLK^)Kolg^Qn1V2yMNu&v^_A4? zp7ti!{n5D9>y7Xw;JW%`Rsm?2?%d?8N{7z1v^!9M!0V$Oq&FC|=S!Fxe458vSBZpsR16RQ=!3IvwtoeDQyM;k&ulPg+RP|cGL2ZK`-oR75o|W zAPbH#ZgD#EEd^`0b(qNYR>FiD-}xl31x((dvrvAw3?n|h*=c&E8mz{7WOr^$#%D9p z4&EwpK+P#;xhjJ|L03i-c(DeGlkR&uUm|?0;|a5Dw>oj2CWd9baST2*7rfhK?Enwk zixEz~*`OBu>cMe>!gS%3>YtY(37f zJN_|5eg1k+n4uf?JoRID(Fn!8kIS6K_|kBp^jqsX;SRpp+v>Zx>I3d(ADu!1L%?L4 z=G<|k8t|8AP#VNeq`5pc{kgaj-n(%gIiQw~aUpf|cdg0%?aWaYy{TF>+q=%ZqqH74 zRcl@~bCLDL^--=+(;CcR6{ZlQ%Y!q@4zxxSDX`GIlfuFi)SLS6QDf5S24nEE9z#W;*^&ftv@ zD~479g&S$t>M>p9(gE|(Jb2%x8o7Rv%+rl#DV9$8fr};&-52$6w7tUKRMZkrddoqv z;}!|n+VtqY!R>l5iDvpULuHNG<7MCG3QN(-&DFMdxB_E78GC zZmEr@2sYar^$yw+&S3KvQSH=vh^IZ%mq~hRY{@m>DT7PFwXkh*;a4o~p%RhgFClYx z{^t0-c76EVVuM0KunJrlI!DUKyCB%@g1((dKPpc0ODSj+!t=+@3$cVRC#O5Ry;iOT zEfsJ5+wX-a8E9lMCOSC(lhGb8y{HA@5*(+g-$@vfCO0pU1T& zHpOE>X*wl)aSy7vIN!fsD2!#-CXBf52Vvd%7Xv|x22{6SWKn3&1JhXMr@a3f&`mA+ zU$S%o*z69Lc|RS4(rq`MgvJLWpWVO0;I?+8d-11e_j)(nH`2}1*&Yi;*&6&?7`>6B zXU}z-l5{Lna{A!)AOg+U3{|JSGr`gGsGZ{uGB@8JBxCn29S5~`x@cV|v;%hAXm-YM ze0<5D{KZCm&6#L(LzXpn!_YpGb6^*z4FsN8PHO5k@vkH;av6w(QFvULHidkN#o2Q%y5(XS?wBwi3>gARvtZY z{e|!TMOPOHXpdWOP)dV0?XOuS4>UmYj{t)f!Z~*tpm2E39SA~w=WLn$S~1tN`A*zi z9{go3wX|T4g6#*XCl#lf!6f31=%2YZSbQ3K^&r1IT2KAlR^J~6|D9JF)r=yXcD{C{ zf8=vkvc{{w#0Ihab87l`hi>2viY$9)9znPt>@w8HTTs!(G-;D>04?uiW(tWSyuxoS z6J)*~d_MS%pK%>rJkoK7P@_?D;-1O&wnq4n)79G2OJH(4j?zw0kn^5(|6CFJI5@1d zv%B#{D$L~xPG+4T9?F5=3Oq$u67iAK!tKajjPbP8&MK5qu)HsJN`R>>E*;^9_pjfd2!3Jkien)7CV zBdi|ty_KSvfv-+bt-m7qv3PkpHC+x3DC8U{<1Z`0t-md2>%)8S&bU{u$E_@&FO?2m zCLRXY$$^tfhEXVZi^o~brVWNR7>rMo_4#sJLV10KKW<36C@=O#!f8^>DJ4FGY$LDv zNZl^97Wq`Mdy~v@6Z^QQ&Bmce%cJ>Qh#&Hoz|MvbeZQ$CeIOwDCFl%QjreE2uad0*dtSHOn4JQak!h`c;)4_gpmUsZxZ;J5* zcd+@_pf_OC(4qYIwbd8;3=fWYoP^oUMGOA38I4#JQG)V-u6&aQ*2zgh!KB z0Y937w(Ay6r_v|fmQSJ9Bi0H{rdD*{!4;OXG-QX!ekC*it!?wx3Xreq>?6b(;EmQ? z*e}zC()vnZurnK8Cm+@DYwgD{DYwWYa!DX|>@)Wzwn{wN_G?Gh&M^GOHFt-dhj7;l zUr!_$G~ko!7j!C-S!f)tuAS@P0P&J>wDF(9K+`zH{kcj4XbM)|{Jgy!FE#7z=&K}s zXGvD})K6y6cqL-5{b&dFzAfH+FO}qgds*Lb=QyFQ>zVj|Dbj1*a>+5bE(E>^IVxp` zzCmH<-cBKB;_Vbw9PPT&4A*mdijof#{<}ccwh6x;P?e^M3ED9LrggDtzwAp;{gYlz z7Xc|+x8cqJqizC5Ke;|6E1 zM2sKbL3*<7k-E{7)%YbiLF(sKUywFRXNPdREc9 z8)BcF3Sx@v!tYeBgFGDlFlF$F)3PuCF2?iie(nw49F)oN&LpAXb^ibu<$J@31ZD1j1> z(9H>l9(a0}v4gsK1arj{1Sh>}?5ZWcn@7R#pDaYf zq3MGBIkj@QDdTt{UbPZaxwzZfYd#``hn+V|ngkC0rP4Z$Eb!mBE~_fliUwI`jSs!Y zfU7+E=YVS->OYqZ;C+F3FmNg7DL>*9Wucw4*U5Tq4_C?tLj@ccRx{EO8vyDb5&hC- zK`^Y_VBL7zAB*auY8M9-6)K~XHGAJG!Dvw@=C#*&59~Kb5UqM z#JLX5e02h@BzOVK_nDky|KkK-$!ppBs0@=Dzx-Lh!|6Cj{ zaW4(abG2f~HYdZZzb)7-D!f{m+yShpR#8)w4L`PNyrlO32okLoZjq_&aBGLrw5#(Z zCS-;dsJhl-{Wq=idpHWfiZ71ZM?Mpcrlnbe`U%(TkQRf>0UvZ)593cG*E@Bxtr-2) zN}Th%JFy^(Fn@2Sde*0xs9wJ*D{fK?`&<9eA0T<5n;%!tuYT(Wl@qpw56L_t|Cqdb zRelm!44F_4ZB${!XDyAWxDKq2`YiEFvKM|2NskV1jRX7cR(q>2r6};L_Z*j22Tap` zJ{)kVh~y<+japHL!$r58Q4hk0RSEqz;mbS$ijC)GPF^AB6>(k+PeLo;@48EWW??qU zdeBrnIFo>^3hHkulymWsv~*W@5#cpH9{s&6I|WreU*oNk@~|WBO3ST6a^CYI=IRNt zMwGi@Y#n}v@X8t(N*sc6fF-6hAn0u^#`i_n#+r`;%c|ZpjjVh4ns_?@j)joYdZcmg+Nh-I#k&=>YL@=B_<&kvhg4cU zn$6tKJK@lT`Wfply&qKKPn4N&>&U^^CC^trydD5v{&JtA3oXF&u|{mS_W;<@D(O{M zko97+$;lTg1#n+9?0G147KSwqPpoCs;dgzlEmX?Bpywbxu5^v$py^U+qV#5!laX%2KC4!IBdTlLqU%ySD18UIKLCSZw-2C7PO$? zf6o{W$CRQw`;Vp1mkNx`X zQ~3eHl3rbU7n6&MJYF2lQ!OY|eDc{=$04wmJnAUXP>vrmPKta%a!&5a)$oRC0L~Ox zS~2EDp)uRXm&#;beVullib1CmJ_jEe&K4*9n^)^1d8CJLUo)YqwNeQNztswF7=7{=iEASW3c6P z#K-T=0T``Pt&{pA4K1I`QTPiLqu^bU>{Ou)c$;R{BTBfhc0#(sUwd<~$vR51BDE5V zsVCI76l9~*=AK`E3xw-3u;i_r?gaW^Wq0<2BeF@pV@f99^OA%s^>q4mczml_XM;mH zt~;5^vt<#I^!q>hm)r-d+rZkxwtYQ(^pypDfdpN^tC7|EXtL zf+5WMrDdMh=sRm@U+0q!967iDO4JQNs^XVJ@6KAFv~T=q9@)3LA1zN}S08}BE4dVu zE;;as+x#|-Y$_VnUOrp7k&Qtep08MBTG2*7_SE{FTu}dBJ7o8?6$E4ax2v45LVGy_ zzGRvby!-3jv$NsVSfDg1>bN5og6h{^%-H3ltV{HS^}}4^HJ?1ao#gs2iht8xCI6p* zQF%j(P7XL%4oSanRzknlTW9?DMd8${B=wv^3idbLKI~>52M*u2dUITe1$L{VU3~vy zP%U=xKqJXTeH+;;J{VSt|Iw_a%#YQeql3j74|O`+r?v7wBv1s3Q6|D0r&IB^m|okg zV-3*Io!43KYsUwYr!Vl9)k3_2{#Z*#3Vdw(-XgAC3Q=oQ;YXuMZvV&XC=u)6+zG}F z)z_&g)#q&}x`7z5F3?7QH3_sH^ypc4W~20u4(bk;Oo)s~85X=-1xkmHYVyQJU=;sv zb&3}mnBXG$c;9+D3Vkwuc2*-4lrMxz&n=fg!s<-f6{2s77K$VYPvTiTj1kWV640FA z=5$)>5PqR+ykF%t0YBN!U6knwg#VH+JsPFRLKW9`?{4iPWY6W6hJGY|&ITuN>$owPsunJ$6>zCZCiA2lqYJ+zPPUFQ6#jir4ML_+2)h@;8 zBfkA+lpLH@jB-i--kUk~@WZI;b^W|1zWaS3%ebcn+$H!f&tA+#!L_m<@??FbZJ-A1 z{q=zI{?BE$H6e3KvBm}MED(QNkZX0O0JNS@YWbEGV@Vj_#(lOSI2bt6nJv3pc1 zQXv^Fuifv7I#&!6Zw@ot94&&6;YXm+xfnlgttxGpR>&mOB$w>1Ud1z6dBy?qCE?JrKKEGfq0 zKD8sMAG@$KLSwaLBNn#Z+Wo}ei=146kWD-hcW=0oeEsRK7u0(6WY8_<#=dj> z@q`!gEb)b3F801Et~HD;LwjebUTmYY4=Gy1qEHcMUYG#`C%qTsi4}z_do=4t))H zS=^w!{9YQ;j!oaxUroV1na4HuH8o*mvq&DLKk*Rf%!lb2q~m9XQcC~t@8OwrapfVo zJRHtD7oUB*2EQC(4tLyi#IB(?wi~@e_&dk1L8zw~*)0$K$umuYUE{v=TGf@Ys1+{D&-!XG<5%J$y^rH2*tw7QVam(>|*SyLQ7ug8Daz9*DNys%(r}6bl>MCt@JOTD3jsNHUzI#b39mGvTqv z;U)7AeaNwzKc%%q{K`WG#qa)>0!NQ1Bd>EBj@kcBrWqebsA(KiQuqwQC6YUIeuQGL z2Vca)yk4XXW%-rc8H*=3`JX)__qT0j9C!Yac~OagqoiF!FY#ZCmzlMv0k_hHFK@|y zq+)n~n#HF|)Ti1dL9>H=o`QXcmWM#vOuF z2gJFXP(Ck0N4T>WPqBQYWReae^D&iToFxfOlnuyZXs5A+155$=gz zO~TQqTEyq3+jJl)m*mxV&)D!~)#0|>7jpdZ4X9(TTX$`w8Um`0uqBY^()Y2Q>Qp_h z=&Py``Y8JyuINoH1^F~%?1cy6`##s=w{&r@r*U<_{8HVjYF`XK*&{hPk#@tRq~wrVI&%)6_y0431(-zWJ{pk@245(R3fzW-r8aPpac##xvncGk~zgv zU?%m>TwK4v<8d{u4pY7L_nJr6VhroXs|+8KZ~NV^dV%nECM?7|1KAPM94|!J7dykk z<1C9@HNunW|Dh$Bn}R#j$1fX{RfFx;@2;=6_2RBw*FL;InvN{G)TOaARk*GDmsrjj zgp-n+Cb#D*Kqv6<_Zzv*C}RIk!hWC%g52^md?qWwqPI0&{B0Fb+WbDEe!32x{=33* z%Doj7F3rx`oEU>-I$PHq)q4EPy+7nS+2?Nxi+10jFTfC6*#F5Q74BZLV=SjkMW6a# zfwm(tcu^+EWyy!Ed$ccIja2G{G6ubIb*W}3W%LQErie%P@a}6iEnOISZ8Y)Qm2&)< zL^C`{SA=u*p|Li+6>wQG+;_At2KgVJO4bhS1Y54?*dwt;5cX|+Qq3g;S#R?W?6a%H zVr}cpF8d^84PE*NpQ_-%gChZ{p;>@8OZ=Z`bwQMU{8_3WT1Zo=hM`k4e;T=c z(ENDRxw7#ZX#C2twbQBsKbSvgm1wKOqaO_Fv$w?q?^BZtaT3kQxVc)OzETT~UbKsm zrk=QX>gO0I_cL(3p=&hgaRrMf-1aorPobQz?rBZdENBi%*0d>V11iHq8UlxNQJo<_ z|5r;Oh+JZG8TuCu)}KPPuwLq`E95#cZabLE;R1hn9?oR_O2?})jd^uTxp?XBp|={NRhZ?iq{ncr z1bQ?Uxv1S6AxLhr+a#?Yww+&D*hWWs8l6cc6<@NU@5{MrGvi=rS~)_WHk%2bwwTbx zwLJs8TB~-)G#QqUB8H!)m5K z{~jfL{vj{LwW@`1!lOE(dE#sw`Y~L$pp-7gfAbIjrE2Ga(qGywQ=OzQBq$ZUbiWB= z!TRWLIpPsvN9pp zWlql>tE`R=+*@C`oWjrxriH+kcQOQHtmgD;MSWpdFsI&>eBSTtf6pHw{PWKfIs%j_ zS@2oxT3J;>F}m8TFc$Kf!n&96EO$>bXeb?OdA+BJ_-b8GKlvJu)oUJ)AMB5Y^9K9M zZ7;{;;zb&YM~1antm|oO%>`+P3c^xqfQu;^+4X2IEzQ zb=!`b2vCx(i>u$)i1%5i&#udQ0FyyP7qJSV7vGaDyHYc7gr?`3<+nyWpS-Z^LqZ4+ zvhWyBoR5e7o7pkNYAtxOj#f(hNdSU4d-G4DIQ(36P(7CH$Jw|tqNh`Hz`iqMgngnM zJ7$)iuLsn^^rn7c#($Bxn&5t*!X+Np)5v=I9r=Fl-KNURR|A5d1_PJ9^I(_fq3_Jh zgiHA74$DEKQVcQTU!qSi$BCm_cXisye5A*a(n25~w=sEMkvv<2hkq>{e^uKLFFru*91!u+PBSiHNwRrPF?NJdTi}0 z8j`2$$GxKel}TO>fhw)(fLc{Mls3y)?eomW4wmub%;TM~eal!hUqU|SB{X-ueKZLV z`>G$jZLkDI&eCE%IwQ>H*`PP<_$n+F0f%>tPsd*S@s499S!;M$JCqaBukm@#43iuqduuAVr0wyXerg9awpWUO?yg2N^#(^HOMmD+bQ>cG zS7nfGzigRIJnVYOqU3kE3=covPfh)=0QCggWOL+Fz;oba#aE6ua9SX_`oI5L@rUxp zE=|!gxWu$GQhhNEQyd;UeckT^8ybl;2iu!bo$-+%{k94my)m?>OFIR6o?cFpPbh=B zm9zrBIl{@EK0p&8rHTW+x>j*3#1AQGd4lS1cGnM?RgQ+dy=biOhLESlzecOX{ z1eM3F;y1~@nC;u2h^cInM`u+vQY8GwQyujX$!zzP&zuvH@tXDV&S{ zScfey@10pV=m9h#AB${07U1rT(@K>>oe<2ivbpku%s0EMNVH!=#lt^->Px`|n*DA8%WM0*DOh6_cm|4ksX;cf;VW0hz1e&4$gSz;I`exj_ z_VCr255>@oBk@DLEjY?I!8l}{gY2b=)TQ@HkLv5S^$^-345c^fJ4^a6F8}Ng{P!di z*&`_3?D^Ax!bnCsBsK@$w~E}DR`>uF)}8cB?q7gGHZE-Aa1N{=h?4>PzkaoG(}zr0<;( z_S7tcgT4$UZZg4SUOc{ZJel|&bT;c!7Rht!N7z0CO*80b%1M@+jRU@{M{llt??Nt~ zUH8qD2@kmEqkw{cGREcfo)+C#2J7qF%Y&bn<4$AgI~OBbP|Aa8_WepXTKRkV{O2!d)zUwg>*OaK2<|kB7jg zOIq^6(faw#4m;@qV7rv~e%D?b@LCiy{VkP_dY;KA_k7I82Ltbx^l1>PSaWatEEj_D z<_-x%;vab>BFenj6bi?tGhdE`XTpu@VP^1MPErV(4{t3#Z4q#wQ9gKZ9i1%ES zKSt1|;EDT(xmq|QNKdn7t&%6Bmu$}{b zw(_AP#>5*-v2M|9;fcpx_S#=N5(T5W5GCY={7mj6K-Yu$3o zxhOck@RUj9eG?Xo1f@juG~xwC_Z#kAY1qy5)#m%jDvbD3qFKt@4V28bufOP&qi$Hq zrh;E7?)Ew2J`mf0D%KPL%I)5#&6KxX&|}8(?NAlm^1OJ;rq=XIxpmT z(R%*qyDF%k`z-O_An_>%F$mxGYlk=6D|pro1MugGncOGVXTeOjdjJWyZAIfq>=0cy z#sm~9io{xBt&LRq+wgj<2`J+3Ah{A-pNSr@K@?d0l_(-R0L(k8ll#J>@Y$Myl*--` z)Tn>(H2i%T6a)l+!>Jzdb$vZI42~P zw8&nM{W619UI#i*KY8(9*hB}45nu?1a2_W#vkqAfQZ3NS0av;E_-K74j}Yxx@i z(xVoComH502ejWNYd9s9KqHUy>(|Du@aK6YT8|FEu^x}uRpRA^C9*_Vk95uw(WP=)4>*C!p8Ap#0dKj;P;Fz+3c-Iyb)pmoooW zJQqy_dakj%p<5>*|5=gc{rD1$I$3!^*QE)#-Q3I+Y+~Wdm#)B}u|b#%{PZwt`#1_r zwK9&@-@@RZLqmyYKA^v{_zZ97Nj3bAx#rHTd*lU+W>K6tw*M z=s-he2RywN_%6^n3nr)4Ja%8K26|4|ADXs^CBrx5=BvD6YHNd5h(R})x~1FItwrH- zV=>EyM>m=`i>8mg%|XVOQ!=*I^+ zCJ?SnF_#r4=Tr9H$ySNcu)_E7oSA78y87%6(j)yZCMP;u`kXBIH0vVYuAL8e6@r(e ztLkB6+-%oLuNKf*Zp;ga$wTAIp-OidV?b|Yf?`M}0d#}rsB%IB;6S0-AGJeyxbUTDn{#hAjzGQ!G8k7X@4?U?bh^hv~FwYLo#waY=VBlP|Xn~0>PJZ&IvWO=+ zk@4R-Unm&Txch~}0q9vc%{-X0P<8L+fcfwS%sA!U^Ldl>32zbX1$_;&?)UgwIo=4d z+t1pc=XsCR=|gVnu4G=^^Vc(w36D<5W7-_ZfeCMk&Y`%2HYeqN*CbnG_eDtXbAFuU(^;%29kzw{z zq91dBlFMYd>P0{3SoTG@Fc6>CaOuHN!moSj=W!w_GXNDdBq%7jgX({;f;+aI{{LV8_bT{*=))^Z literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/API/Base/APIError.swift b/Starbucks/Starbucks/Sources/API/Base/APIError.swift index f79d737..cfaf066 100644 --- a/Starbucks/Starbucks/Sources/API/Base/APIError.swift +++ b/Starbucks/Starbucks/Sources/API/Base/APIError.swift @@ -9,17 +9,17 @@ import Foundation enum APIError: Error { case custom(message: String, debugMessage: String) - case jsonMapping(response: Response) case objectMapping(error: Error, response: Response) case underlying(error: Swift.Error, response: Response?) + case statusCode(response: Response) case unowned } extension APIError { var statusCode: Int { switch self { - case .jsonMapping(let response), - .objectMapping(_, let response): + case .objectMapping(_, let response), + .statusCode(let response): return response.statusCode case .underlying(_, let response): return response?.statusCode ?? -9999 diff --git a/Starbucks/Starbucks/Sources/API/Base/Response.swift b/Starbucks/Starbucks/Sources/API/Base/Response.swift index 5ebbd20..27df968 100644 --- a/Starbucks/Starbucks/Sources/API/Base/Response.swift +++ b/Starbucks/Starbucks/Sources/API/Base/Response.swift @@ -24,9 +24,6 @@ class Response { extension Response { func map(_ type: D.Type, using decoder: JSONDecoder = JSONDecoder()) throws -> D { - if data.count < 1 { - throw APIError.jsonMapping(response: self) - } do { return try decoder.decode(D.self, from: data) } catch { @@ -66,16 +63,7 @@ extension PrimitiveSequence where Trait == SingleTrait, Element == Result [AVCaptureDevice] { + let deviceDiscoverySession = AVCaptureDevice.DiscoverySession( + deviceTypes: types, + mediaType: .video, + position: position + ) + + return deviceDiscoverySession.devices + } + + func startCamera(config: Config) -> AVCaptureSession? { + if captureSession.isRunning { + captureSession.stopRunning() + } + + captureSession.beginConfiguration() + + let newCameraDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [config.cameraType], mediaType: .video, position: config.position).devices.first + + guard let cameraDevice = newCameraDevice, + let videoInput = try? AVCaptureDeviceInput(device: cameraDevice) else { + return nil + } + + if captureSession.canAddInput(videoInput) { + captureSession.addInput(videoInput) + } + + videoDataOutput.setSampleBufferDelegate(self, queue: config.queue) + videoDataOutput.alwaysDiscardsLateVideoFrames = false + videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey: config.formatType] as [String: Any] + + if captureSession.canAddOutput(videoDataOutput) { + captureSession.addOutput(videoDataOutput) + } + + captureSession.commitConfiguration() + captureSession.startRunning() + + return captureSession + } + + func captureBuffer() -> CMSampleBuffer? { + guard let buffer = currentBuffer else { + return nil + } + + return buffer + } + + func stopSession() { + captureSession.stopRunning() + } +} + +extension CameraSession: AVCaptureVideoDataOutputSampleBufferDelegate { + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + currentBuffer = sampleBuffer + delegate?.cameraCaptureOutput(didOutput: sampleBuffer) + } +} diff --git a/Starbucks/Starbucks/Sources/Common/Camera/CameraSessionError.swift b/Starbucks/Starbucks/Sources/Common/Camera/CameraSessionError.swift new file mode 100644 index 0000000..dd5733e --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/Camera/CameraSessionError.swift @@ -0,0 +1,12 @@ +// +// CameraSessionError.swift +// Starbucks +// +// Created by seongha shin on 2022/05/16. +// + +import Foundation + +enum CameraSessionError: Error { + case unowned +} diff --git a/Starbucks/Starbucks/Sources/Common/Camera/PreviewView.swift b/Starbucks/Starbucks/Sources/Common/Camera/PreviewView.swift new file mode 100644 index 0000000..29904a7 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/Camera/PreviewView.swift @@ -0,0 +1,19 @@ +// +// PreviewView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/16. +// + +import AVFoundation +import UIKit + +class PreviewView: UIView { + override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } + + var videoPreviewLayer: AVCaptureVideoPreviewLayer? { + layer as? AVCaptureVideoPreviewLayer + } +} diff --git a/Starbucks/Starbucks/Sources/Common/Container.swift b/Starbucks/Starbucks/Sources/Common/Container.swift index 38681df..df0b9b0 100644 --- a/Starbucks/Starbucks/Sources/Common/Container.swift +++ b/Starbucks/Starbucks/Sources/Common/Container.swift @@ -14,5 +14,9 @@ class Container { lazy var starbucksRepository: StarbucksRepository = StarbucksRepositoryImpl() + lazy var cameraRepository: CameraRepository = CameraRepositoryImpl() + lazy var imageManager = ImageManager() + + lazy var userStore = UserStore() } diff --git a/Starbucks/Starbucks/Sources/Common/PropertyWrapper/UserDefault.swift b/Starbucks/Starbucks/Sources/Common/PropertyWrapper/UserDefault.swift new file mode 100644 index 0000000..d21b71e --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/PropertyWrapper/UserDefault.swift @@ -0,0 +1,37 @@ +// +// UserDefault.swift +// Starbucks +// +// Created by seongha shin on 2022/05/17. +// + +import Foundation + +@propertyWrapper +struct UserDefault { + let key: UserDefault.Key + let defaultValue: Value? + var container: UserDefaults = .standard + + var wrappedValue: Value? { + get { + container.object(forKey: key.value) as? Value ?? defaultValue + } + set { + container.set(newValue, forKey: key.value) + } + } + + init(key: UserDefault.Key, defaultValue: Value? = nil) { + self.key = key + self.defaultValue = defaultValue + } +} + +extension UserDefault { + enum Key: String { + case cardList + + var value: String { rawValue } + } +} diff --git a/Starbucks/Starbucks/Sources/Common/UserStore.swift b/Starbucks/Starbucks/Sources/Common/UserStore.swift new file mode 100644 index 0000000..e98bd31 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/UserStore.swift @@ -0,0 +1,36 @@ +// +// UserStore.swift +// Starbucks +// +// Created by seongha shin on 2022/05/17. +// + +import Foundation + +class UserStore { + + private let encoder = JSONEncoder() + private let decoder = JSONDecoder() + + @UserDefault(key: UserDefault.Key.cardList) private var cardListData: Data? + + var cardList: [StarbucksEntity.Card] { + get { + getValue([StarbucksEntity.Card].self, data: self.cardListData) ?? [] + } set { + guard let data = try? encoder.encode(newValue) else { + return + } + self.cardListData = data + } + } + + private func getValue(_ type: T.Type, data: Data?) -> T? { + guard let data = data, + let value = try? decoder.decode(T.self, from: data) else { + return nil + } + + return value + } +} diff --git a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift index 9209560..dc8db49 100644 --- a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -36,6 +36,10 @@ extension StarbucksEntity { case imageUploadPath = "img_UPLOAD_PATH" case thumbnail = "mob_THUM" } + + var imageUrl: URL { + imageUploadPath.appendingPathComponent(thumbnail) + } } } @@ -54,6 +58,10 @@ extension StarbucksEntity { case imageUploadPath = "img_UPLOAD_PATH" case thumbnail = "mob_THUM" } + + var imageUrl: URL { + imageUploadPath.appendingPathComponent("/upload/promotion/" + thumbnail) + } } } @@ -112,3 +120,27 @@ extension StarbucksEntity { } } } + +extension StarbucksEntity { + struct CardList: Codable { + let cards: [Card] + } + + struct Card: Codable { + let id: String + let name: String + let cardImageUrl: URL + private(set) var amount: Int + + init(name: String, cardImageUrl: URL, amount: Int) { + self.id = UUID().uuidString + self.name = name + self.cardImageUrl = cardImageUrl + self.amount = amount + } + + mutating func addAmount(_ amount: Int) { + self.amount += amount + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift b/Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift new file mode 100644 index 0000000..5c6c04d --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift @@ -0,0 +1,64 @@ +// +// NavigationBarView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/17. +// + +import UIKit + +class NavigationBarView: UIView { + + private let navigationView = UIView() + + private let titleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.font = .systemFont(ofSize: 24, weight: .bold) + label.textColor = .black + label.alpha = 0 + return label + }() + + var title: String = "" { + didSet { + titleLabel.text = title + } + } + + override init(frame: CGRect) { + super.init(frame: .zero) + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("Init with coder is unavailable") + } + + private func attribute() { + backgroundColor = .white + } + + private func layout() { + let topSafeAreaInset = UIApplication.shared.windows[0].safeAreaInsets.top + + addSubview(navigationView) + navigationView.addSubview(titleLabel) + + navigationView.snp.makeConstraints { + $0.top.equalToSuperview().offset(topSafeAreaInset) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(topSafeAreaInset) + } + + titleLabel.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } + + func setAlpha(_ alpha: CGFloat) { + titleLabel.alpha = alpha + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index b96e79b..5add8dc 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -14,7 +14,7 @@ class HomeViewController: UIViewController { enum Constants { static let stickyViewHeight = 50.0 static let homeInfoViewHeight = 250.0 - static let bottomOffset = 100.0 + static let bottomOffset = 150.0 } private let scrollView: UIScrollView = { @@ -105,10 +105,7 @@ class HomeViewController: UIViewController { .disposed(by: disposeBag) viewModel.state().titleMessage - .withUnretained(self) - .bind(onNext: { vc, message in - vc.homeInfoView.setMessage(message) - }) + .bind(onNext: homeInfoView.setMessage) .disposed(by: disposeBag) viewModel.state().presentProductDetailView diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift index 823e372..a35f357 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -54,7 +54,7 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA private let disposeBag = DisposeBag() private var homeData: StarbucksEntity.Home? - init() { + init() { let requestHome = action().loadHome .withUnretained(self) .flatMapLatest { model, _ in @@ -77,18 +77,23 @@ class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelA requestHome .compactMap { $0.value?.displayName } - .withUnretained(self) - .bind(onNext: { model, name in - model.titleMessage.accept("\(name)님\n오늘 하루도 고생 많으셨어요!") - let myRecommandTitle = NSMutableAttributedString().addStrings([ - .create(name, options: [.foreground(color: .brown1)]), - .create("님을 위한 추천 메뉴") - ]) - model.recommandMenuViewModel.state().displayTitle.accept(myRecommandTitle) - - let timeRecommandtitle = NSMutableAttributedString(string: "이 시간대 추천 메뉴") - model.timeRecommandMenuViewModel.state().displayTitle.accept(timeRecommandtitle) - }) + .map { "\($0)님\n오늘 하루도 고생 많으셨어요!" } + .bind(to: titleMessage) + .disposed(by: disposeBag) + + requestHome + .compactMap { $0.value?.displayName } + .map { NSMutableAttributedString().addStrings([ + .create($0, options: [.foreground(color: .brown1)]), + .create("님을 위한 추천 메뉴") + ]) + } + .bind(to: recommandMenuViewModel.state().displayTitle) + .disposed(by: disposeBag) + + requestHome + .map { _ in NSMutableAttributedString(string: "이 시간대 추천 메뉴") } + .bind(to: timeRecommandMenuViewModel.state().displayTitle) .disposed(by: disposeBag) requestHome diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift index 5004a28..ad7b636 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift @@ -40,21 +40,8 @@ class MainEventViewModel: MainEventViewModelBinding, MainEventViewModelAction, M init() { loadedEvent - .map { $0.imageUploadPath.appendingPathComponent($0.thumbnail) } + .map { $0.imageUrl } .bind(to: loadedMainEventImage) .disposed(by: disposeBag) - -// let requestEvent = action().loadEvent -// .withUnretained(self) -// .flatMapLatest { model, _ in -// model.starbucksRepository.requestEvent() -// } -// .share() -// -// requestEvent -// .compactMap { $0.value } -// .bind(onNext: { -// }) -// .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift index 07193e4..061bd38 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift @@ -47,7 +47,7 @@ class RecommandMenuDataSource: NSObject, UICollectionViewDataSource { var attributedStrings: [NSAttributedString] = [] if type == .thisTime { - let indexText: NSAttributedString = .create("\(index + 1)", options: [.font(.systemFont(ofSize: 20, weight: .bold))]) + let indexText: NSAttributedString = .create("\(index + 1) ", options: [.font(.systemFont(ofSize: 20, weight: .bold))]) attributedStrings.append(indexText) } attributedStrings.append(.create(product.productName)) diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift index 0a4c5e4..8beb897 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift @@ -61,8 +61,7 @@ class WhatsNewCellView: UICollectionViewCell { titleLabel.text = title } - func setThumbnail(uploadPath: URL, thumbnailName: String) { - let url = uploadPath.appendingPathComponent("/upload/promotion/" + thumbnailName) + func setThumbnail(url: URL) { imageManager.loadImage(url: url).asObservable() .withUnretained(self) .observe(on: MainScheduler.asyncInstance) diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift index 60d1872..9af77a4 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewDataSource.swift @@ -25,7 +25,7 @@ class WhatsNewDataSource: NSObject, UICollectionViewDataSource { let event = events[indexPath.item] cell.setTitle(event.title) - cell.setThumbnail(uploadPath: event.imageUploadPath, thumbnailName: event.thumbnail) + cell.setThumbnail(url: event.imageUrl) return cell } } diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift index a24f8aa..41d68a5 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift @@ -65,12 +65,12 @@ class WhatsNewViewController: UIViewController { .bind(to: viewModel.action().loadEvent) .disposed(by: disposeBag) - viewModel.state().loadedEvent - .withUnretained(self) - .bind(onNext: { vc, events in - vc.dataSource.updateEvents(events) - vc.eventCollectionView.reloadData() - }) + viewModel.state().updateEvents + .bind(onNext: dataSource.updateEvents) + .disposed(by: disposeBag) + + viewModel.state().reloadEvents + .bind(onNext: eventCollectionView.reloadData) .disposed(by: disposeBag) seeAllButton.rx.tap diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift index afb0daf..6035e1c 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift @@ -16,7 +16,8 @@ protocol WhatsNewViewModelAction { } protocol WhatsNewViewModelState { - var loadedEvent: PublishRelay<[StarbucksEntity.Promotion]> { get } + var updateEvents: PublishRelay<[StarbucksEntity.Promotion]> { get } + var reloadEvents: PublishRelay { get } } protocol WhatsNewViewModelBinding { @@ -36,7 +37,8 @@ class WhatsNewViewModel: WhatsNewViewModelBinding, WhatsNewViewModelAction, What func state() -> WhatsNewViewModelState { self } - let loadedEvent = PublishRelay<[StarbucksEntity.Promotion]>() + let updateEvents = PublishRelay<[StarbucksEntity.Promotion]>() + let reloadEvents = PublishRelay() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() @@ -51,7 +53,9 @@ class WhatsNewViewModel: WhatsNewViewModelBinding, WhatsNewViewModelAction, What requestEvent .compactMap { $0.value?.list } - .bind(to: loadedEvent) + .do { [weak self] list in self?.updateEvents.accept(list) } + .map { _ in } + .bind(to: reloadEvents) .disposed(by: disposeBag) Observable diff --git a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift index d8fc32c..0124b67 100644 --- a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift @@ -5,7 +5,200 @@ // Created by seongha shin on 2022/05/09. // +import AVFoundation +import RxSwift import UIKit class PayViewController: UIViewController { + + private let customNavigationBarView: NavigationBarView = { + let view = NavigationBarView() + view.title = "Pay" + return view + }() + + private let scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.isPagingEnabled = false + scrollView.showsHorizontalScrollIndicator = true + scrollView.contentInsetAdjustmentBehavior = .never + return scrollView + }() + + private let contentStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 20 + return stackView + }() + + private let titleView = UIView() + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "Pay" + label.textAlignment = .left + label.font = .systemFont(ofSize: 24, weight: .bold) + label.textColor = .black + return label + }() + + private lazy var cardListViewController: CardListViewController = { + let viewController = CardListViewController(viewModel: viewModel.cardListViewModel) + return viewController + }() + + private let tappedAddCard: UIBarButtonItem = { + let button = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .plain, target: nil, action: nil) + return button + }() + + private let chargeView: UIView = { + let view = UIView() + view.isHidden = true + return view + }() + + private let previewView = PreviewView() + private let captureButton: UIButton = { + let button = UIButton() + button.clipsToBounds = true + button.backgroundColor = .white + button.layer.cornerRadius = 50 + return button + }() + + private let viewModel: PayViewModelProtocol + private let disposeBag = DisposeBag() + + init(viewModel: PayViewModelProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + bind() + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + rx.viewDidAppear + .map { _ in } + .withUnretained(self) + .bind(onNext: { vc, _ in + vc.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) + vc.navigationController?.navigationBar.shadowImage = UIImage() + vc.navigationController?.navigationBar.backgroundColor = .clear + vc.navigationItem.rightBarButtonItem = vc.tappedAddCard + }) + .disposed(by: disposeBag) + + viewModel.state().presentChargeView + .observe(on: MainScheduler.instance) + .withUnretained(self) + .bind(onNext: { vc, session in + vc.previewView.videoPreviewLayer?.session = session + }) + .disposed(by: disposeBag) + + viewModel.state().isHiddenChargeView + .withUnretained(self) + .bind(onNext: { vc, isHidden in + vc.navigationController?.navigationBar.isHidden = !isHidden + vc.tabBarController?.tabBar.isHidden = !isHidden + vc.chargeView.isHidden = isHidden + }) + .disposed(by: disposeBag) + + captureButton.rx.tap + .bind(to: viewModel.action().captureCamera) + .disposed(by: disposeBag) + + tappedAddCard.rx.tap + .bind(to: viewModel.action().tappedAddCard) + .disposed(by: disposeBag) + + viewModel.state().showChargeEffect + .bind(onNext: { amount in + print("\(amount)원 충전 됫다오!") + }) + .disposed(by: disposeBag) + } + + private func attribute() { + scrollView.delegate = self + } + + private func layout() { + view.addSubview(scrollView) + view.addSubview(customNavigationBarView) + view.addSubview(chargeView) + + scrollView.addSubview(contentStackView) + contentStackView.addArrangedSubview(titleView) + contentStackView.addArrangedSubview(cardListViewController.view) + titleView.addSubview(titleLabel) + chargeView.addSubview(previewView) + chargeView.addSubview(captureButton) + + customNavigationBarView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.top) + } + + scrollView.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide) + $0.leading.trailing.bottom.equalToSuperview() + } + + scrollView.contentLayoutGuide.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.bottom.equalTo(contentStackView).offset(300) + } + + contentStackView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview() + } + + titleView.snp.makeConstraints { + $0.height.equalTo(titleLabel) + } + + titleLabel.snp.makeConstraints { + $0.top.bottom.equalToSuperview() + $0.leading.equalToSuperview().offset(20) + } + + chargeView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + previewView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + captureButton.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalTo(view.safeAreaLayoutGuide) + $0.width.height.equalTo(100) + } + } +} + +extension PayViewController: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + var alpha: CGFloat = 0 + if scrollView.contentOffset.y < 0 { + alpha = 0 + } else { + alpha = scrollView.contentOffset.y / titleLabel.frame.size.height + alpha = alpha > 1 ? 1 : alpha + } + + customNavigationBarView.setAlpha(alpha) + } } diff --git a/Starbucks/Starbucks/Sources/Present/Pay/PayViewModel.swift b/Starbucks/Starbucks/Sources/Present/Pay/PayViewModel.swift new file mode 100644 index 0000000..60fa2ca --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Pay/PayViewModel.swift @@ -0,0 +1,108 @@ +// +// PayViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/15. +// + +import AVFoundation +import RxRelay +import RxSwift + +protocol PayViewModelAction { + var tappedAddCard: PublishRelay { get } + var startCamera: PublishRelay { get } + var captureCamera: PublishRelay { get } +} + +protocol PayViewModelState { + var presentChargeView: PublishRelay { get } + var isHiddenChargeView: PublishRelay { get } + var showChargeEffect: PublishRelay { get } +} + +protocol PayViewModelBinding { + func action() -> PayViewModelAction + func state() -> PayViewModelState +} + +protocol PayViewModelProperty { + var cardListViewModel: CardListViewModelProtocol { get } +} + +typealias PayViewModelProtocol = PayViewModelBinding & PayViewModelProperty + +class PayViewModel: PayViewModelBinding, PayViewModelProperty, PayViewModelAction, PayViewModelState { + func action() -> PayViewModelAction { self } + + let tappedAddCard = PublishRelay() + let startCamera = PublishRelay() + let captureCamera = PublishRelay() + + func state() -> PayViewModelState { self } + + let presentChargeView = PublishRelay() + let isHiddenChargeView = PublishRelay() + let showChargeEffect = PublishRelay() + + let cardListViewModel: CardListViewModelProtocol = CardListViewModel() + + @Inject(\.cameraRepository) private var cameraReposition: CameraRepository + private let disposeBag = DisposeBag() + private let koreaCashMlModel = KoreaCash() + + init() { + tappedAddCard + .bind(to: cardListViewModel.action().tappedAddCard) + .disposed(by: disposeBag) + + let requestCamera = cardListViewModel.action().tappedCashCharge + .withUnretained(self) + .flatMapLatest { model, _ in + model.cameraReposition.startCamera(config: CameraSession.Config(), delegate: nil) + } + .compactMap { $0.value } + .share() + + requestCamera + .map { _ in false } + .bind(to: isHiddenChargeView) + .disposed(by: disposeBag) + + requestCamera + .bind(to: presentChargeView) + .disposed(by: disposeBag) + + let mlResult = captureCamera + .withUnretained(self) + .flatMapLatest { model, _ in + model.cameraReposition.captureBuffer() + } + .withUnretained(self) + .do { model, _ in + model.cameraReposition.stopCamera() + } + .compactMap { model, buffer -> Int? in + guard let buffer = buffer.value, + let cvBuffer = CMSampleBufferGetImageBuffer(buffer), + let mlPrediction = try? model.koreaCashMlModel.prediction(image: cvBuffer) else { + return nil + } + return Int(mlPrediction.classLabel) + } + .share() + + mlResult + .bind(to: cardListViewModel.action().chargedCash) + .disposed(by: disposeBag) + + mlResult + .withUnretained(self) + .do { model, _ in + model.isHiddenChargeView.accept(true) + } + .map { $0.1 } + .bind(to: showChargeEffect) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewCell.swift b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewCell.swift new file mode 100644 index 0000000..1ac7990 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewCell.swift @@ -0,0 +1,198 @@ +// +// CardListItemView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/17. +// + +import RxSwift +import UIKit + +class CardListViewCell: UICollectionViewCell { + static let identifier = "CardListItemView" + + private let shadowView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.layer.shadowOpacity = 0.3 + view.layer.shadowOffset = CGSize(width: 0, height: 5) + view.layer.shadowRadius = 5.0 + view.layer.shadowColor = UIColor.black.cgColor + view.layer.masksToBounds = false + return view + }() + + private let borderView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + private let cardImage: UIImageView = { + let imageView = UIImageView() + imageView.clipsToBounds = true + imageView.backgroundColor = .clear + imageView.layer.cornerRadius = 10 + imageView.layer.borderColor = UIColor.gray.cgColor + imageView.layer.borderWidth = 1 + return imageView + }() + + private let nameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12) + return label + }() + + private let amountLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 23, weight: .regular) + return label + }() + + private let barcodeView: UIImageView = { + let imageView = UIImageView() + imageView.backgroundColor = .black.withAlphaComponent(0.5) + return imageView + }() + + private let timerLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 12) + return label + }() + + private let cashChargeButton: UIButton = { + let button = UIButton() + button.setTitle("충전하기", for: .normal) + button.setTitleColor(.black, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 20) + return button + }() + + @Inject(\.imageManager) private var imageManager: ImageManager + private let disposeBag = DisposeBag() + private var timer: Timer? + private var tappedChargeEvent: (() -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + bind() + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("Init with coder is unavailable") + } + + private func bind() { + cashChargeButton.rx.tap + .withUnretained(self) + .bind(onNext: { cell, _ in cell.tappedChargeEvent?() }) + .disposed(by: disposeBag) + } + + private func attribute() { + } + + private func layout() { + contentView.addSubview(shadowView) + contentView.addSubview(borderView) + borderView.addSubview(cardImage) + borderView.addSubview(nameLabel) + borderView.addSubview(amountLabel) + borderView.addSubview(barcodeView) + borderView.addSubview(timerLabel) + borderView.addSubview(cashChargeButton) + + shadowView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview().inset(15) + $0.bottom.equalTo(cashChargeButton).offset(30) + } + + borderView.snp.makeConstraints { + $0.edges.equalTo(shadowView) + } + + cardImage.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview().inset(10) + $0.height.equalTo(cardImage.snp.width).multipliedBy(0.63) + } + + nameLabel.snp.makeConstraints { + $0.top.equalTo(cardImage.snp.bottom).offset(20) + $0.centerX.equalToSuperview() + } + + amountLabel.snp.makeConstraints { + $0.top.equalTo(nameLabel.snp.bottom).offset(5) + $0.centerX.equalToSuperview() + } + + barcodeView.snp.makeConstraints { + $0.top.equalTo(amountLabel.snp.bottom).offset(10) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.equalTo(50) + } + + timerLabel.snp.makeConstraints { + $0.top.equalTo(barcodeView.snp.bottom).offset(15) + $0.centerX.equalToSuperview() + } + + cashChargeButton.snp.makeConstraints { + $0.top.equalTo(timerLabel.snp.bottom).offset(10) + $0.leading.trailing.equalToSuperview().inset(20) + } + } + + func setName(_ name: String) { + nameLabel.text = name + } + + func setAmount(_ amount: Int) { + amountLabel.text = "\(amount)원" + } + + func setThumbnail(url: URL) { + imageManager.loadImage(url: url).asObservable() + .withUnretained(self) + .observe(on: MainScheduler.asyncInstance) + .bind(onNext: { cell, image in + cell.cardImage.image = image + }) + .disposed(by: disposeBag) + } + + func startTimer() { + if timer != nil { + timer?.invalidate() + timer = nil + } + let format = DateFormatter() + format.dateFormat = "mm:ss" + + let endTimer = Date().addingTimeInterval(600) + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in + let diffTime = endTimer - Date().timeIntervalSince1970 + if diffTime.timeIntervalSince1970 < 0 { + timer.invalidate() + self?.timer = nil + return + } + + let timeText = format.string(from: diffTime) + self?.timerLabel.attributedText = NSMutableAttributedString() + .addStrings([ + NSAttributedString.create("바코드 유효시간 "), + NSAttributedString.create(timeText, options: [.foreground(color: .starbuckGreen)]) + ]) + } + } + + func registChargeEvent(_ event: @escaping () -> Void) { + tappedChargeEvent = event + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewController.swift b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewController.swift new file mode 100644 index 0000000..5c25443 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewController.swift @@ -0,0 +1,91 @@ +// +// CardListViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/17. +// + +import RxSwift +import UIKit + +class CardListViewController: UIViewController { + + private let cardCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .horizontal + flowLayout.minimumLineSpacing = 0 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.isScrollEnabled = true + collectionView.register(CardListViewCell.self, forCellWithReuseIdentifier: CardListViewCell.identifier) + collectionView.showsHorizontalScrollIndicator = false + collectionView.isPagingEnabled = true + return collectionView + }() + + private let viewModel: CardListViewModelProtocol + private let disposeBag = DisposeBag() + private let dataSource = CardListViewDataSource() + + init(viewModel: CardListViewModelProtocol) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + + bind() + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + + rx.viewDidLoad + .bind(to: viewModel.action().loadCardList) + .disposed(by: disposeBag) + + viewModel.state().updateCardList + .compactMap { $0 } + .bind(onNext: dataSource.updateCards) + .disposed(by: disposeBag) + + viewModel.state().reloadCardList + .bind(onNext: cardCollectionView.reloadData) + .disposed(by: disposeBag) + + dataSource.tappedCashCharge + .bind(to: viewModel.action().tappedCashCharge) + .disposed(by: disposeBag) + + viewModel.state().reloadCard + .map { [IndexPath(item: $0, section: 0)] } + .bind(onNext: cardCollectionView.reloadItems) + .disposed(by: disposeBag) + } + + private func attribute() { + cardCollectionView.delegate = self + cardCollectionView.dataSource = dataSource + cardCollectionView.reloadData() + } + + private func layout() { + view.addSubview(cardCollectionView) + + view.snp.makeConstraints { + $0.height.equalTo(500) + } + + cardCollectionView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } +} + +extension CardListViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + view.frame.size + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewDataSource.swift new file mode 100644 index 0000000..31d7a14 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewDataSource.swift @@ -0,0 +1,39 @@ +// +// CardListViewDataSource.swift +// Starbucks +// +// Created by seongha shin on 2022/05/17. +// + +import Foundation +import RxSwift +import UIKit + +class CardListViewDataSource: NSObject, UICollectionViewDataSource { + private var cards: [StarbucksEntity.Card] = [] + let tappedCashCharge = PublishSubject() + + func updateCards(_ cards: [StarbucksEntity.Card]) { + self.cards = cards + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + cards.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CardListViewCell.identifier, for: indexPath) as? CardListViewCell else { + return UICollectionViewCell() + } + + let card = cards[indexPath.item] + cell.setName(card.name) + cell.setAmount(card.amount) + cell.setThumbnail(url: card.cardImageUrl) + cell.startTimer() + cell.registChargeEvent { [weak self] in + self?.tappedCashCharge.onNext(indexPath.item) + } + return cell + } +} diff --git a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift index 1aef749..f25c6df 100644 --- a/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -21,7 +21,7 @@ class StarbucksViewController: UITabBarController { homeViewController.tabBarItem.title = "Home" homeViewController.tabBarItem.image = UIImage(named: "ic_temp") - let payViewController = PayViewController() + let payViewController = UINavigationController(rootViewController: PayViewController(viewModel: PayViewModel())) payViewController.tabBarItem.title = "Pay" payViewController.tabBarItem.image = UIImage(named: "ic_temp") diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift index 101fcf4..60d35a1 100644 --- a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift @@ -46,12 +46,12 @@ class WhatsNewListViewController: UIViewController { .bind(to: viewModel.action().loadEvents) .disposed(by: disposeBag) - viewModel.state().loadedEvents - .withUnretained(self) - .bind(onNext: { vc, events in - vc.dataSource.updateEvents(events) - vc.tableView.reloadData() - }) + viewModel.state().updateEvents + .bind(onNext: dataSource.updateEvents) + .disposed(by: disposeBag) + + viewModel.state().reloadEvents + .bind(onNext: tableView.reloadData) .disposed(by: disposeBag) } diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift index 3bc5fd3..4c16b86 100644 --- a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift @@ -14,7 +14,8 @@ protocol WhatsNewListViewAction { } protocol WhatsNewListViewState { - var loadedEvents: PublishRelay<[StarbucksEntity.Event]> { get } + var updateEvents: PublishRelay<[StarbucksEntity.Event]> { get } + var reloadEvents: PublishRelay { get } } protocol WhatsNewListViewBinding { @@ -31,7 +32,8 @@ class WhatsNewListViewModel: WhatsNewListViewProtocol, WhatsNewListViewAction, W func state() -> WhatsNewListViewState { self } - let loadedEvents = PublishRelay<[StarbucksEntity.Event]>() + let updateEvents = PublishRelay<[StarbucksEntity.Event]>() + let reloadEvents = PublishRelay() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() @@ -49,7 +51,10 @@ class WhatsNewListViewModel: WhatsNewListViewProtocol, WhatsNewListViewAction, W } .scan(self.events, accumulator: +) .map { $0.sorted(by: { lhs, rhs in lhs.startDate > rhs.startDate }) } - .bind(to: loadedEvents) + .withUnretained(self) + .do { model, list in model.updateEvents.accept(list) } + .map { _ in } + .bind(to: reloadEvents) .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Repository/Camera/CameraRepository.swift b/Starbucks/Starbucks/Sources/Repository/Camera/CameraRepository.swift new file mode 100644 index 0000000..a1e814d --- /dev/null +++ b/Starbucks/Starbucks/Sources/Repository/Camera/CameraRepository.swift @@ -0,0 +1,16 @@ +// +// CameraREpository.swift +// Starbucks +// +// Created by seongha shin on 2022/05/15. +// + +import AVFoundation +import RxSwift + +protocol CameraRepository { + func findCameraDevices() -> [AVCaptureDevice] + func startCamera(config: CameraSession.Config, delegate: CameraSessionDelegate?) -> Single> + func captureBuffer() -> Single> + func stopCamera() +} diff --git a/Starbucks/Starbucks/Sources/Repository/Camera/CameraRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Camera/CameraRepositoryImpl.swift new file mode 100644 index 0000000..43c5264 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Repository/Camera/CameraRepositoryImpl.swift @@ -0,0 +1,45 @@ +// +// CameraRepositoryImpl.swift +// Starbucks +// +// Created by seongha shin on 2022/05/15. +// + +import AVFoundation +import RxSwift + +class CameraRepositoryImpl: CameraRepository { + private let cameraSession = CameraSession() + private let captureOutput: () -> Void = { } + + func findCameraDevices() -> [AVCaptureDevice] { + + cameraSession.findCameraDevices([.builtInWideAngleCamera, .builtInTelephotoCamera, .builtInUltraWideCamera], position: .unspecified) + } + + func startCamera(config: CameraSession.Config, delegate: CameraSessionDelegate? = nil) -> Single> { + Single.create { [weak self] observer in + guard let session = self?.cameraSession.startCamera(config: config) else { + observer(.success(.failure(.unowned))) + return Disposables.create { } + } + self?.cameraSession.delegate = delegate + observer(.success(.success(session))) + return Disposables.create { } + } + } + + func captureBuffer() -> Single> { + Single.create { [weak self] observer in + guard let buffer = self?.cameraSession.captureBuffer() else { + return Disposables.create { } + } + observer(.success(.success(buffer))) + return Disposables.create { } + } + } + + func stopCamera() { + cameraSession.stopSession() + } +} diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift index d2dc6d9..0d17d53 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -36,16 +36,18 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo func requestCategory() -> Single> { Single.create { observer in guard let url = Bundle.main.url(forResource: "Category", withExtension: "json"), - let data = try? Data(contentsOf: url), - let category = try? JSONDecoder().decode(Category.self, from: data) else { - - let response = Response(statusCode: -999, data: Data()) - let error = APIError.jsonMapping(response: response) - observer(.success(.failure(error))) + let data = try? Data(contentsOf: url) else { + observer(.success(.failure(.unowned))) return Disposables.create { } } - observer(.success(.success(category.groups))) + let response = Response(statusCode: 200, data: data) + do { + let category = try response.map(Category.self) + observer(.success(.success(category.groups))) + } catch { + observer(.success(.failure(APIError.objectMapping(error: error, response: response)))) + } return Disposables.create { } } } From ec6dd71727990edf3c3932dad8be1ed2f840fb3d Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 18 May 2022 10:58:33 +0900 Subject: [PATCH 39/57] =?UTF-8?q?=EB=88=84=EB=9D=BD=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pay/View/CardList/CardListViewModel.swift | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewModel.swift diff --git a/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewModel.swift b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewModel.swift new file mode 100644 index 0000000..b94810b --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Pay/View/CardList/CardListViewModel.swift @@ -0,0 +1,96 @@ +// +// CardListViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/17. +// + +import Foundation +import RxRelay +import RxSwift + +protocol CardListViewModelAction { + var loadCardList: PublishRelay { get } + var tappedAddCard: PublishRelay { get } + var tappedCashCharge: PublishRelay { get } + var chargedCash: PublishRelay { get } +} + +protocol CardListViewModelState { + var updateCardList: PublishRelay<[StarbucksEntity.Card]?> { get } + var reloadCardList: PublishRelay { get } + var reloadCard: PublishRelay { get } +} + +protocol CardListViewModelBinding { + func action() -> CardListViewModelAction + func state() -> CardListViewModelState +} + +typealias CardListViewModelProtocol = CardListViewModelBinding + +class CardListViewModel: CardListViewModelBinding, CardListViewModelAction, CardListViewModelState { + + func action() -> CardListViewModelAction { self } + + let loadCardList = PublishRelay() + let tappedAddCard = PublishRelay() + let tappedCashCharge = PublishRelay() + let chargedCash = PublishRelay() + + func state() -> CardListViewModelState { self } + + let updateCardList = PublishRelay<[StarbucksEntity.Card]?>() + let reloadCardList = PublishRelay() + let reloadCard = PublishRelay() + + @Inject(\.userStore) private var userStore: UserStore + private let disposeBag = DisposeBag() + private var cardList: [StarbucksEntity.Card] = [] + + init() { + loadCardList + .withUnretained(self) + .compactMap { model, _ in model.userStore.cardList } + .withUnretained(self) + .do { model, cardList in + model.cardList = cardList + model.updateCardList.accept(cardList) + } + .map { _ in } + .bind(to: reloadCardList) + .disposed(by: disposeBag) + + tappedAddCard + .compactMap { _ -> StarbucksEntity.Card? in + guard let imageUrl = URL(string: "https://image.istarbucks.co.kr/cardImg/20200818/007633_WEB.png") else { + return nil + } + return StarbucksEntity.Card(name: "카드5", cardImageUrl: imageUrl, amount: 10000) + } + .withUnretained(self) + .do { model, newCard in + var cardList = model.cardList + cardList.append(newCard) + model.cardList = cardList + model.userStore.cardList = cardList + model.updateCardList.accept(cardList) + } + .map { model, _ in model.cardList.count - 1 } + .bind(to: reloadCard) + .disposed(by: disposeBag) + + chargedCash + .withLatestFrom(tappedCashCharge) { ($0, $1) } + .withUnretained(self) + .do { model, result in + let (amount, index) = result + model.cardList[index].addAmount(amount) + model.userStore.cardList = model.cardList + model.updateCardList.accept(model.cardList) + } + .map { $1.1 } + .bind(to: reloadCard) + .disposed(by: disposeBag) + } +} From d44e22df0e2f47486e3e9271557bc101491bc245 Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Mon, 16 May 2022 18:48:26 +0900 Subject: [PATCH 40/57] =?UTF-8?q?[STAR-21]=20feat:=20groupID=20=EB=A5=BC?= =?UTF-8?q?=20=EC=9D=B4=EC=9A=A9=ED=95=B4=20Repository=20=EC=97=90=20Categ?= =?UTF-8?q?oryProduct=20=EC=9D=98=20=EB=B0=B0=EC=97=B4=EC=9D=84=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=ED=95=98=EB=8A=94=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 4 + .../xcshareddata/xcschemes/Starbucks.xcscheme | 88 ++++++++++++++++++ Starbucks/Starbucks/Resource/.DS_Store | Bin 0 -> 6148 bytes .../Starbucks/Resource/samepleColdBrew.json | 3 + .../Sources/API/StarbucksTarget.swift | 11 ++- .../Sources/Model/CategoryProductEntity.swift | 25 +++++ .../OrderCategory/OrderViewModel.swift | 26 ++++++ .../Starbucks/StarbucksRepository.swift | 1 + .../Starbucks/StarbucksRepositoryImpl.swift | 8 ++ 9 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 Starbucks/Starbucks.xcodeproj/xcshareddata/xcschemes/Starbucks.xcscheme create mode 100644 Starbucks/Starbucks/Resource/.DS_Store create mode 100644 Starbucks/Starbucks/Resource/samepleColdBrew.json create mode 100644 Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index a373718..cbbfc17 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 006FA0922832433A00281C4D /* CategoryProductEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0912832433A00281C4D /* CategoryProductEntity.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0039B55282FD92800298719 /* WhatsNewListViewController.swift */; }; @@ -89,6 +90,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 006FA0912832433A00281C4D /* CategoryProductEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryProductEntity.swift; sourceTree = ""; }; 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.debug.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.debug.xcconfig"; sourceTree = ""; }; 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -454,6 +456,7 @@ children = ( E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */, E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */, + 006FA0912832433A00281C4D /* CategoryProductEntity.swift */, ); path = Model; sourceTree = ""; @@ -793,6 +796,7 @@ E0108CF6283365CE00CC736C /* UserStore.swift in Sources */, E07233B8282B7DB900AF3E16 /* NetworkRepository.swift in Sources */, E07233DE282BDDB200AF3E16 /* RecommandMenuDataSource.swift in Sources */, + 006FA0922832433A00281C4D /* CategoryProductEntity.swift in Sources */, E07233F8282D516200AF3E16 /* GradientView.swift in Sources */, E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, E0108CFB28338D3A00CC736C /* NavigationBarView.swift in Sources */, diff --git a/Starbucks/Starbucks.xcodeproj/xcshareddata/xcschemes/Starbucks.xcscheme b/Starbucks/Starbucks.xcodeproj/xcshareddata/xcschemes/Starbucks.xcscheme new file mode 100644 index 0000000..3406fdd --- /dev/null +++ b/Starbucks/Starbucks.xcodeproj/xcshareddata/xcschemes/Starbucks.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Starbucks/Starbucks/Resource/.DS_Store b/Starbucks/Starbucks/Resource/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6c6fa7dc3b9e931ae24f25b2adceabe962c66ef2 GIT binary patch literal 6148 zcmeHKK~BRk5S%SN6d^pOaW8C6!?b< zux6``P6AqM3YY?>K%;>E9~@mVjaUctzYaG32td?rq&N0;lc*dQF^yOUDlt$c z9x)`)IqqUy8nF%-=#Y5$kT|o%6N>oRncw+vNE*;uQ@|8xD{vGp$6Eid51;?rB)c*N zOo2b8fDC6hvy874YisZ2wAMQMEnQ9H>VOS}9bbwWtEKpY?v3M4JH#|%9grh5+X#3W KtT6?CRDn->vP=;G literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Resource/samepleColdBrew.json b/Starbucks/Starbucks/Resource/samepleColdBrew.json new file mode 100644 index 0000000..5b213d2 --- /dev/null +++ b/Starbucks/Starbucks/Resource/samepleColdBrew.json @@ -0,0 +1,3 @@ +{"list":[{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"�ㅽ�踰낆뒪�� 肄쒕뱶 釉뚮(�� ������ 誘쇳듃 珥덉퐫 踰좎씠�ㅻ줈\r\n�꾧뎄�� 利먭만 �� �덈뒗 �щ쫫 �뚮즺.\r\n�먮ぉ�� �ㅻ깄�� 異ㅼ쓣 異붾벏 媛�蹂띻쾶 �뚮젮 �뚮즺瑜� �욎뼱��\r\n鍮⑤� �놁씠 利먭꺼 蹂댁꽭��.","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000003988","product_NM":"濡ㅻ┛ 誘쇳듃 珥덉퐫 肄쒕뱶 釉뚮(","file_NAME":"[9200000003988]_20220406113215251.jpg","file_PATH":"/upload/store/skuimg/2022/04/[9200000003988]_20220406113215251.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"210","fat":"7","nut_TABLE":"","new_SDATE":"20220411","new_EDATE":"","sell_CAT":"1","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"Y","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"4.5","trans_FAT":"","cholesterol":"","sugars":"28","chabo":"30","protein":"6","sodium":"115","caffeine":"131","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"遺��쒕윭�� 紐⑸꽆源��� �섏씠�몃줈 而ㅽ뵾�� 諛붾땺�� �щ┝�� 留ㅻ젰�� �쒕쾲�� �먭뺨蹂댁꽭��!","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000002487","product_NM":"�섏씠�몃줈 諛붾땺�� �щ┝","file_NAME":"[9200000002487]_20210426091745467.jpg","file_PATH":"/upload/store/skuimg/2021/04/[9200000002487]_20210426091745467.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"80","fat":"2.7","nut_TABLE":"","new_SDATE":"20190927","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"2","trans_FAT":"","cholesterol":"","sugars":"10","chabo":"10","protein":"1","sodium":"40","caffeine":"232","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"�섏씠�몃줈 而ㅽ뵾 �뺥넻�� 罹먯뒪耳��대뵫怨� 遺��쒕윭�� 肄쒕뱶 �щ젅留�!\r\n遺��쒕윭�� 紐� �섍�怨� �꾨꼍�� 諛몃윴�ㅼ뿉 而ㅽ뵾 蹂몄뿰�� �⑤쭧�� 寃쏀뿕�� �� �덉뒿�덈떎.\r\n","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000000479","product_NM":"�섏씠�몃줈 肄쒕뱶 釉뚮(","file_NAME":"[9200000000479]_20210426091843897.jpg","file_PATH":"/upload/store/skuimg/2021/04/[9200000000479]_20210426091843897.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"5","fat":"0","nut_TABLE":"","new_SDATE":"20170328","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"0","trans_FAT":"","cholesterol":"","sugars":"0","chabo":"0","protein":"0","sodium":"5","caffeine":"245","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"臾대뜑�� �щ쫫泥�, \r\n�숇궓�� �닿�吏��먯꽌 利먭린�� 而ㅽ뵾瑜� �좎삤瑜닿쾶 �섎뒗 \r\n�ㅽ�踰낆뒪 �뚮즺�� 踰좎뒪�� x 踰좎뒪�� 議고빀��\r\n�뚯껜 肄쒕뱶 釉뚮(瑜� 留뚮굹蹂댁꽭��! ","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000002081","product_NM":"�뚯껜 肄쒕뱶 釉뚮(","file_NAME":"[9200000002081]_20210415133656839.jpg","file_PATH":"/upload/store/skuimg/2021/04/[9200000002081]_20210415133656839.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"265","fat":"12","nut_TABLE":"","new_SDATE":"20190416","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"9","trans_FAT":"","cholesterol":"","sugars":"29","chabo":"29","protein":"8","sodium":"130","caffeine":"150","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"肄쒕뱶 釉뚮(�� �뷀빐吏� 諛붾땺�� �щ┝�쇰줈 源붾걫�섎㈃�� �ъ숴�� 肄쒕뱶 釉뚮(瑜� �덈∼寃� 利먭만 �� �덈뒗 �뚮즺�낅땲��.","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000000487","product_NM":"諛붾땺�� �щ┝ 肄쒕뱶 釉뚮(","file_NAME":"[9200000000487]_20210430112319040.jpg","file_PATH":"/upload/store/skuimg/2021/04/[9200000000487]_20210430112319040.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"125","fat":"8","nut_TABLE":"","new_SDATE":"20170417","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"6","trans_FAT":"","cholesterol":"","sugars":"11","chabo":"11","protein":"3","sodium":"58","caffeine":"150","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"�ㅽ겕 珥덉퐳由� 紐⑥뭅�� 吏꾪븳 諛붾뵒媛먭낵 �④퍡\r\n�ㅼ씠利먮꽋 �κ낵 �ъ숴�� 移대씪硫� �щ┝ �쇱쑝濡�\r\n踰⑤껙媛숈� 遺��쒕윭��源뚯� �대┛ 由ъ�釉� �섏씠�몃줈 而ㅽ뵾","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000003509","product_NM":"踰⑤껙 �ㅽ겕 紐⑥뭅 �섏씠�몃줈","file_NAME":"[9200000003509]_20210322093452399.jpg","file_PATH":"/upload/store/skuimg/2021/03/[9200000003509]_20210322093452399.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"150","fat":"7","nut_TABLE":"","new_SDATE":"20210326","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"6","trans_FAT":"","cholesterol":"","sugars":"17","chabo":"20","protein":"2","sodium":"15","caffeine":"190","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"肄쒕뱶 釉뚮( �꾩슜 �먮몢瑜� 李④��� 臾쇰줈 14�쒓컙 �숈븞 異붿텧�섏뿬 遺��쒕읇怨� 吏꾪븳 �띾��� 肄쒕뱶釉뚮(瑜� �쒕━踰꾨━濡� �먰븯�� 怨녹뿉�� �명븯寃� 利먭꺼蹂댁꽭�� (�꾩슜 由ъ쑀��釉� 蹂댄� /500ml)","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000003661","product_NM":"�쒓렇�덉쿂 �� 釉붾옓 肄쒕뱶 釉뚮(","file_NAME":"[9200000003661]_20210819094346176.jpg","file_PATH":"/upload/store/skuimg/2021/08/[9200000003661]_20210819094346176.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"25","fat":"0","nut_TABLE":"","new_SDATE":"20210824","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"0","trans_FAT":"","cholesterol":"","sugars":"0","chabo":"0","protein":"0","sodium":"50","caffeine":"680","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"�쒖< 泥쒕뀈�� �� 鍮꾩옄由쇱쓣 �곗긽�쒗궎�� �뚮즺濡� �쒖< �좉린�� 留먯감�� 肄쒕뱶 釉뚮(媛� 議고솕濡쒖슫 �쒖< �뱁솕 肄쒕뱶 釉뚮( �뚮즺","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000002672","product_NM":"�쒖< 鍮꾩옄由� 肄쒕뱶 釉뚮(","file_NAME":"[9200000002672]_20220311105511600.jpg","file_PATH":"/upload/store/skuimg/2022/03/[9200000002672]_20220311105511600.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"340","fat":"12","nut_TABLE":"","new_SDATE":"20200318","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"8","trans_FAT":"","cholesterol":"","sugars":"43","chabo":"46","protein":"10","sodium":"140","caffeine":"105","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"�ㅽ�踰낆뒪 諛붾━�ㅽ��� �뺤꽦�쇰줈 �꾩깮�� 肄쒕뱶 釉뚮(!\r\n\r\n肄쒕뱶 釉뚮( �꾩슜 �먮몢瑜� 李④��� 臾쇰줈 14�쒓컙 �숈븞 異붿텧�섏뿬 �쒖젙�� �묐쭔 �쒓났�⑸땲��.\r\n源딆� �띾��� �덈줈�� 而ㅽ뵾 寃쏀뿕�� 利먭꺼蹂댁꽭��.","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000000038","product_NM":"肄쒕뱶 釉뚮(","file_NAME":"[9200000000038]_20210430113202458.jpg","file_PATH":"/upload/store/skuimg/2021/04/[9200000000038]_20210430113202458.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"5","fat":"0","nut_TABLE":"","new_SDATE":"20160418","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"0","trans_FAT":"","cholesterol":"","sugars":"0","chabo":"0","protein":"0","sodium":"11","caffeine":"150","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"[由ъ�釉� �꾩슜�뚮즺] 由ъ�釉� 肄쒕뱶 釉뚮(, 諛붾땺�� �꾩씠�ㅽ겕由�, 紐고듃媛� 釉붾젋�⑸맂 由ъ�釉뚮쭔�� �먯씠��\r\n","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000001636","product_NM":"肄쒕뱶 釉뚮( 紐고듃","file_NAME":"[9200000001636]_20210225093600536.jpg","file_PATH":"/upload/store/skuimg/2021/02/[9200000001636]_20210225093600536.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"505","fat":"30","nut_TABLE":"","new_SDATE":"20180726","new_EDATE":"","sell_CAT":"0","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"20","trans_FAT":"","cholesterol":"","sugars":"41","chabo":"51","protein":"7","sodium":"150","caffeine":"190","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"肄쒕뱶 釉뚮(�� �띾��� 源붾걫�� �ㅽ듃 諛��ш� �댁슦�ъ쭊 �ъ숴 怨좎냼�� �쇰뼹. \r\n�앸Ъ�� 諛��щ� �ъ슜�� 紐⑤뱺 怨좉컼�� 遺��댁뾾�� 利먭만 �� �덈뒗 肄쒕뱶 釉뚮( �뚮즺","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000003285","product_NM":"肄쒕뱶 釉뚮( �ㅽ듃 �쇰뼹","file_NAME":"[9200000003285]_20210416154437069.jpg","file_PATH":"/upload/store/skuimg/2021/04/[9200000003285]_20210416154437069.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"105","fat":"3.6","nut_TABLE":"","new_SDATE":"20210429","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":".3","trans_FAT":"","cholesterol":"","sugars":"11","chabo":"16","protein":"1","sodium":"95","caffeine":"65","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0},{"cate_CD":"","del_YN":"","reg_USER":"","reg_DT":"","mod_USER":"","mod_DT":"","content":"[由ъ�釉� �꾩슜�뚮즺] 由ъ�釉� 肄쒕뱶 釉뚮( �꾩뿉 �뱀븘 �대━�� �� �ㅼ엽�� 諛붾땺�� �꾩씠�ㅽ겕由�","cate_TYPE":"","pro_SEQ":0,"view_YN":"","info_TABLE":"","file_TABLE":"","cate_TABLE":"","card_TABLE":"","msr_SEQ":0,"web_IMAGE_WEBVIEW":"","web_IMAGE_TABVIEW":"","web_IMAGE_MOBVIEW":"","product_ENGNM":"","product_CD":"9200000001635","product_NM":"肄쒕뱶 釉뚮( �뚮줈��","file_NAME":"[9200000001635]_20210225092236748.jpg","file_PATH":"/upload/store/skuimg/2021/02/[9200000001635]_20210225092236748.jpg","cate_NAME":"肄쒕뱶 釉뚮(","recommend":"","kcal":"225","fat":"15","nut_TABLE":"","new_SDATE":"20180726","new_EDATE":"","sell_CAT":"","gift_VALUE":"","hot_YN":"","price":"","sold_OUT":"N","front_VIEW_YN":"","view_SDATE":"","view_EDATE":"","note_TYPE":"","youtube_CODE":"","newicon":"N","recomm":"0","file_MASTER":"","thumbnail":"","img_ORDER":"","standard":"","unit":"","sat_FAT":"10","trans_FAT":"","cholesterol":"","sugars":"18","chabo":"21","protein":"3","sodium":"70","caffeine":"190","allergy":"","kcal_L":"","fat_L":"","sat_FAT_L":"0","trans_FAT_L":"","cholesterol_L":"","sugars_L":"0","chabo_L":"","protein_L":"","sodium_L":"","caffeine_L":"0","msr_SEQ2":"","web_NEW":"","app_IMAGE":"","web_CARD_BIRTH":"","card_YEAR":"","card_MONTH":"","egift_CARD_YN":"","pair_TABLE":"","sub_VIEW":"","f_CATE_CD":"//W0000001","pcate_CD":"","all_CATE_CD":"","img_UPLOAD_PATH":"https://www.istarbucks.co.kr","premier":"N","search_DATE_TYPE":null,"search_START_DATE":null,"search_END_DATE":null,"search_1_CATE":null,"search_2_CATE":null,"search_3_CATE":null,"search_SALE_TYPE":null,"search_VIEW_YN":null,"search_KEY":"","search_VALUE":null,"theme_SEARCH":"N","page_INDEX":1,"page_UNIT":20,"page_SIZE":10,"first_INDEX":1,"last_INDEX":1,"record_COUNT_PER_PAGE":10,"total_CNT":0,"rnum":0}] + +} diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift index 5ed9a78..864a5ea 100644 --- a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -14,6 +14,7 @@ enum StarbucksTarget: BaseTarget { case requestNotice case requestProductInfo(_ id: String) case requestProductImage(_ id: String) + case requestCategoryProduct(_ id: String) } extension StarbucksTarget { @@ -22,7 +23,7 @@ extension StarbucksTarget { switch self { case .requestHome: return URL(string: "https://api.codesquad.kr/starbuckst") - case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice: + case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice, .requestCategoryProduct: return URL(string: "https://www.starbucks.co.kr") } } @@ -41,12 +42,14 @@ extension StarbucksTarget { return "/whats_new/newsListAjax.do" case .requestNotice: return "/whats_new/noticeListAjax.do" + case .requestCategoryProduct(let id): + return "/upload/json/menu/\(id).js" } } var parameter: [String: Any]? { switch self { - case .requestHome, .requestNews, .requestNotice: + case .requestHome, .requestNews, .requestNotice, .requestCategoryProduct: return nil case .requestPromotion: return ["MENU_CD": "all"] @@ -61,14 +64,14 @@ extension StarbucksTarget { switch self { case .requestHome, .requestNews, .requestNotice: return .get - case .requestPromotion, .requestProductInfo, .requestProductImage: + case .requestPromotion, .requestProductInfo, .requestProductImage, .requestCategoryProduct: return .post } } var content: HTTPContentType { switch self { - case .requestHome: + case .requestHome, .requestCategoryProduct: return .json case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice: return .urlencode diff --git a/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift new file mode 100644 index 0000000..7d58195 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift @@ -0,0 +1,25 @@ +// +// CategoryProductEntity.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/16. +// + +import Foundation + +struct CategoryProductEntity: Codable { + let products: [Product] +} + +// MARK: - Product +struct Product: Codable { + let productCD, productNM, filePATH: String + let imgUPLOADPATH: String + + enum CodingKeys: String, CodingKey { + case productCD = "product_CD" + case productNM = "product_NM" + case filePATH = "file_PATH" + case imgUPLOADPATH = "img_UPLOAD_PATH" + } +} diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 2177754..bb6b2b7 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -13,6 +13,8 @@ protocol OrderViewModelAction { var loadCategory: PublishRelay { get } var tappedCategory: PublishRelay { get } var tappedMenu: PublishRelay { get } + var loadCategoryProducts: PublishRelay { get } + } protocol OrderViewModelState { @@ -35,6 +37,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB let loadCategory = PublishRelay() let tappedCategory = PublishRelay() let tappedMenu = PublishRelay() + let loadCategoryProducts = PublishRelay() func state() -> OrderViewModelState { self } @@ -89,5 +92,28 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB .compactMap { model, type in model.categoryMenu[type] } //선택한 카테고리 데이터 반환 .bind(to: loadedCategory) //선택한 카테고리 데이터 전달 .disposed(by: disposeBag) + + action().tappedMenu + .withLatestFrom(tappedCategory) { [weak self] in + self?.categoryMenu[$1]?[$0] + } + .compactMap { $0?.groupId } + .bind(to: loadCategoryProducts) + .disposed(by: disposeBag) + + // TODO: 아래 string 을 받는 Observable 을 이용하는 방법을 참조하자. + let requestCategoryProduct = action().loadCategoryProducts + .share() +// loadedProducts +// .withUnretained(self) +// .flatMapLatest { model, ids in +// Observable.zip( ids.map { id in +// model.starbucksRepository.requestDetail(id).asObservable() +// .compactMap { $0.value } +// }) +// } +// .map { $0.compactMap { $0.view } } +// .bind(to: loadedRecommandMenu) +// .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift index 8339366..f934c89 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -16,4 +16,5 @@ protocol StarbucksRepository { func requestCategory() -> Single> func requestNews() -> Single> func requestNotice() -> Single> + func requestCategoryProduct(id: String) -> Single> } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift index 0d17d53..97bf2b1 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -64,3 +64,11 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo .map(StarbucksEntity.Events.self) } } + +extension StarbucksRepositoryImpl { + func requestCategoryProduct(id: String) -> Single> { + provider + .request(.requestCategoryProduct(id)) + .map(CategoryProductEntity.self) + } +} From 76c64630f749b83e9b1301e7d3e6595c6b377a2e Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Tue, 17 May 2022 00:58:57 +0900 Subject: [PATCH 41/57] =?UTF-8?q?[STAR-21]=20feat:=20Category=20Product=20?= =?UTF-8?q?array=EB=A5=BC=20API=20=EB=A1=9C=20=EB=B6=80=ED=84=B0=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=A4=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/API/StarbucksTarget.swift | 8 +++---- .../Sources/Model/CategoryProductEntity.swift | 20 +++++++++------- .../OrderCategory/OrderViewModel.swift | 23 +++++-------------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift index 864a5ea..f5718c8 100644 --- a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -62,18 +62,18 @@ extension StarbucksTarget { var method: HTTPMethod { switch self { - case .requestHome, .requestNews, .requestNotice: + case .requestHome, .requestNews, .requestNotice, .requestCategoryProduct: return .get - case .requestPromotion, .requestProductInfo, .requestProductImage, .requestCategoryProduct: + case .requestPromotion, .requestProductInfo, .requestProductImage : return .post } } var content: HTTPContentType { switch self { - case .requestHome, .requestCategoryProduct: + case .requestHome: return .json - case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice: + case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice, .requestCategoryProduct: return .urlencode } } diff --git a/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift index 7d58195..fbe151f 100644 --- a/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift @@ -7,19 +7,23 @@ import Foundation -struct CategoryProductEntity: Codable { +struct CategoryProductEntity: Decodable { let products: [Product] + + enum CodingKeys: String, CodingKey { + case products = "list" + } } // MARK: - Product -struct Product: Codable { - let productCD, productNM, filePATH: String - let imgUPLOADPATH: String +struct Product: Decodable { + let productCode, productName, filePath: String + let imgUploadPath: URL enum CodingKeys: String, CodingKey { - case productCD = "product_CD" - case productNM = "product_NM" - case filePATH = "file_PATH" - case imgUPLOADPATH = "img_UPLOAD_PATH" + case productCode = "product_CD" + case productName = "product_NM" + case filePath = "file_PATH" + case imgUploadPath = "img_UPLOAD_PATH" } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index bb6b2b7..7aed646 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -14,7 +14,6 @@ protocol OrderViewModelAction { var tappedCategory: PublishRelay { get } var tappedMenu: PublishRelay { get } var loadCategoryProducts: PublishRelay { get } - } protocol OrderViewModelState { @@ -98,22 +97,12 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB self?.categoryMenu[$1]?[$0] } .compactMap { $0?.groupId } - .bind(to: loadCategoryProducts) + .withUnretained(self) + .flatMapLatest { model, id in + model.starbucksRepository.requestCategoryProduct(id: id).asObservable() + } + .compactMap { $0.value } + .subscribe(onNext: { print($0.products) }) .disposed(by: disposeBag) - - // TODO: 아래 string 을 받는 Observable 을 이용하는 방법을 참조하자. - let requestCategoryProduct = action().loadCategoryProducts - .share() -// loadedProducts -// .withUnretained(self) -// .flatMapLatest { model, ids in -// Observable.zip( ids.map { id in -// model.starbucksRepository.requestDetail(id).asObservable() -// .compactMap { $0.value } -// }) -// } -// .map { $0.compactMap { $0.view } } -// .bind(to: loadedRecommandMenu) -// .disposed(by: disposeBag) } } From 618e441db81679b2ff59bb1f69f49e5c4470528c Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Tue, 17 May 2022 17:01:28 +0900 Subject: [PATCH 42/57] =?UTF-8?q?[STAR-19]=20feat:=20listVC=20=EC=97=90=20?= =?UTF-8?q?Cell=20=EC=9D=84=20=ED=83=AD=ED=95=A0=20=EC=8B=9C=20DetailVC=20?= =?UTF-8?q?=EB=A5=BC=20=ED=98=B8=EC=B6=9C=ED=95=98=EB=8A=94=20=ED=9D=90?= =?UTF-8?q?=EB=A6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 18 +++++- .../Sources/Model/CategoryProductEntity.swift | 7 ++- .../OrderCategoryViewController.swift | 17 +++++- .../OrderCategory/OrderViewModel.swift | 16 +++-- .../OrderListTableViewDataSource.swift | 31 ++++++++++ .../OrderListTableViewDelegate.swift | 20 +++++++ .../OrderList/OrderListViewController.swift | 59 +++++++++++++++++-- .../OrderList/OrderListViewModel.swift | 51 ++++++++++++++++ 8 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift create mode 100644 Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDelegate.swift create mode 100644 Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index cbbfc17..1ac2306 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 006FA0922832433A00281C4D /* CategoryProductEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0912832433A00281C4D /* CategoryProductEntity.swift */; }; + 006FA0942833408200281C4D /* OrderListTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0932833408200281C4D /* OrderListTableViewDataSource.swift */; }; + 006FA0962833775400281C4D /* OrderListTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0952833775400281C4D /* OrderListTableViewDelegate.swift */; }; + 006FA0982833785B00281C4D /* OrderListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0972833785B00281C4D /* OrderListViewModel.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0039B55282FD92800298719 /* WhatsNewListViewController.swift */; }; @@ -91,6 +94,9 @@ /* Begin PBXFileReference section */ 006FA0912832433A00281C4D /* CategoryProductEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryProductEntity.swift; sourceTree = ""; }; + 006FA0932833408200281C4D /* OrderListTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderListTableViewDataSource.swift; sourceTree = ""; }; + 006FA0952833775400281C4D /* OrderListTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderListTableViewDelegate.swift; sourceTree = ""; }; + 006FA0972833785B00281C4D /* OrderListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderListViewModel.swift; sourceTree = ""; }; 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.debug.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.debug.xcconfig"; sourceTree = ""; }; 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -392,6 +398,9 @@ isa = PBXGroup; children = ( E072338B282B7DB900AF3E16 /* OrderListViewController.swift */, + 006FA0972833785B00281C4D /* OrderListViewModel.swift */, + 006FA0952833775400281C4D /* OrderListTableViewDelegate.swift */, + 006FA0932833408200281C4D /* OrderListTableViewDataSource.swift */, ); path = OrderList; sourceTree = ""; @@ -399,11 +408,11 @@ E072338C282B7DB900AF3E16 /* OrderCategory */ = { isa = PBXGroup; children = ( - E072338D282B7DB900AF3E16 /* OrderTableViewDelegate.swift */, - E072338E282B7DB900AF3E16 /* OrderTableViewDataSource.swift */, E072338F282B7DB900AF3E16 /* OrderCategoryViewController.swift */, - E0723390282B7DB900AF3E16 /* CategoryTableViewCell.swift */, E0723391282B7DB900AF3E16 /* OrderViewModel.swift */, + E0723390282B7DB900AF3E16 /* CategoryTableViewCell.swift */, + E072338D282B7DB900AF3E16 /* OrderTableViewDelegate.swift */, + E072338E282B7DB900AF3E16 /* OrderTableViewDataSource.swift */, ); path = OrderCategory; sourceTree = ""; @@ -774,8 +783,10 @@ files = ( E07233E3282CA71B00AF3E16 /* MainEventViewController.swift in Sources */, E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */, + 006FA0982833785B00281C4D /* OrderListViewModel.swift in Sources */, E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */, E07233AF282B7DB900AF3E16 /* OrderDetailViewController.swift in Sources */, + 006FA0962833775400281C4D /* OrderListTableViewDelegate.swift in Sources */, E07233BA282B7DB900AF3E16 /* Result+Extension.swift in Sources */, E0108CF82833784800CC736C /* CardListViewDataSource.swift in Sources */, E07233C8282B7F5200AF3E16 /* CategoryEntity.swift in Sources */, @@ -814,6 +825,7 @@ E07233B9282B7DB900AF3E16 /* UIColor+Extension.swift in Sources */, E07233EE282D0F9300AF3E16 /* WhatsNewDataSource.swift in Sources */, E0108CD4282FEE4D00CC736C /* WhatsNewListDataSource.swift in Sources */, + 006FA0942833408200281C4D /* OrderListTableViewDataSource.swift in Sources */, E07233E6282D039600AF3E16 /* WhatsNewViewController.swift in Sources */, E07233BD282B7DB900AF3E16 /* StarbucksEntity.swift in Sources */, E0108CE42831E8D600CC736C /* CameraSessionError.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift index fbe151f..e3797ea 100644 --- a/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift @@ -19,11 +19,16 @@ struct CategoryProductEntity: Decodable { struct Product: Decodable { let productCode, productName, filePath: String let imgUploadPath: URL - + let productCategory: String + var completeUrl: URL { imgUploadPath.appendingPathComponent(filePath) } + let newBadge: String + enum CodingKeys: String, CodingKey { case productCode = "product_CD" case productName = "product_NM" case filePath = "file_PATH" case imgUploadPath = "img_UPLOAD_PATH" + case productCategory = "cate_NAME" + case newBadge = "newicon" } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index 5cea466..95cf2a1 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -25,6 +25,12 @@ class OrderCategoryViewController: UIViewController { return label }() + private let backBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) + barButtonItem.tintColor = .systemGray + return barButtonItem + }() + private let categoryView: UIView = { let view = UIView() view.layer.borderWidth = 1 @@ -78,7 +84,7 @@ class OrderCategoryViewController: UIViewController { .disposed(by: disposeBag) tableViewHandler.selectedCellIndex - .bind(to: viewModel.action().tappedMenu) + .bind(to: viewModel.action().tapMenu) .disposed(by: disposeBag) viewModel.state().selectedCategory @@ -90,6 +96,15 @@ class OrderCategoryViewController: UIViewController { }) .disposed(by: disposeBag) + viewModel.state().loadedList + .withUnretained(self) + .subscribe(onNext: { model, list in + let orderListVC = OrderListViewController(viewModel: OrderListViewModel(list: list)) + model.navigationItem.backBarButtonItem = model.backBarButtonItem + model.navigationController?.pushViewController(orderListVC, animated: true) + }) + .disposed(by: disposeBag) + categoryButtons.enumerated().forEach { index, button in button.rx.tap .compactMap { _ in diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 7aed646..920c0e8 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -12,14 +12,14 @@ import RxSwift protocol OrderViewModelAction { var loadCategory: PublishRelay { get } var tappedCategory: PublishRelay { get } - var tappedMenu: PublishRelay { get } - var loadCategoryProducts: PublishRelay { get } + var tapMenu: PublishRelay { get } } protocol OrderViewModelState { var loadedCategory: PublishRelay<[Category.Group]> { get } var selectedCategory: PublishRelay { get } var selectedMenu: PublishRelay { get } + var loadedList: PublishRelay<[Product]> { get } } protocol OrderViewModelBinding { @@ -35,14 +35,14 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB let loadCategory = PublishRelay() let tappedCategory = PublishRelay() - let tappedMenu = PublishRelay() - let loadCategoryProducts = PublishRelay() + let tapMenu = PublishRelay() func state() -> OrderViewModelState { self } let loadedCategory = PublishRelay<[Category.Group]>() let selectedCategory = PublishRelay() let selectedMenu = PublishRelay() + let loadedList = PublishRelay<[Product]>() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository @@ -92,7 +92,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB .bind(to: loadedCategory) //선택한 카테고리 데이터 전달 .disposed(by: disposeBag) - action().tappedMenu + let requestProducts = action().tapMenu .withLatestFrom(tappedCategory) { [weak self] in self?.categoryMenu[$1]?[$0] } @@ -101,8 +101,12 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB .flatMapLatest { model, id in model.starbucksRepository.requestCategoryProduct(id: id).asObservable() } + .share() + + requestProducts .compactMap { $0.value } - .subscribe(onNext: { print($0.products) }) + .map { $0.products } + .bind(to: loadedList) .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift new file mode 100644 index 0000000..815859f --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift @@ -0,0 +1,31 @@ +// +// OrderListTableViewDataSource.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import UIKit +import RxSwift +import RxRelay + +class OrderListTableViewDataSource: NSObject, UITableViewDataSource { + + private var list: [Product] = [] + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return list.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: CategoryTableViewCell.identifier, for: indexPath) + as? CategoryTableViewCell else { + return UITableViewCell() + } + let indexCell = list[indexPath.row] + cell.setMenuName(text: indexCell.productName) +// cell.setSubName(text: <#T##String#>) + cell.setThumbnail(url: indexCell.completeUrl) + return cell + } +} diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDelegate.swift new file mode 100644 index 0000000..3156ef7 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDelegate.swift @@ -0,0 +1,20 @@ +// +// OrderListTableViewDelegate.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/17. +// + +import Foundation +import RxRelay +import RxSwift + +class OrderListTableViewDelegate: NSObject, UITableViewDelegate { + + let selectedCellIndex = PublishSubject() + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + selectedCellIndex + .onNext(indexPath.row) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index 00f678e..8844ab5 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -6,12 +6,31 @@ // import UIKit +import RxSwift +import SnapKit class OrderListViewController: UIViewController { - init(title: String) { + let tableView = UITableView() + let tableViewDatasource = OrderListTableViewDataSource() + let tableViewHandler = OrderListTableViewDelegate() + let viewModel: ListViewModelProtocol + + let categoryLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 28, weight: .bold) + return label + }() + + private let disposeBag = DisposeBag() + + init(viewModel: ListViewModelProtocol) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) - self.title = title + attribute() + // TODO: VM 에서 list 의 첫번째 값을 categoryLabel.text 에 저장 + layout() + bind() } @available(*, unavailable) @@ -19,12 +38,42 @@ class OrderListViewController: UIViewController { fatalError("init(coder:) has not been implemented") } - override func viewDidLoad() { - super.viewDidLoad() - attribute() + private func bind() { + tableViewHandler.selectedCellIndex + .subscribe(onNext: { + print($0) + }) + .disposed(by: disposeBag) } private func attribute() { view.backgroundColor = .systemBackground + navigationController?.navigationBar.shadowImage = UIImage() + configureTableView() + } + + private func layout() { + view.addSubview(tableView) + view.addSubview(categoryLabel) + + categoryLabel.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(10) + $0.trailing.equalToSuperview() + $0.leading.equalToSuperview().offset(20) + } + + tableView.snp.makeConstraints { + $0.top.equalTo(categoryLabel.snp.bottom).offset(20) + $0.bottom.leading.trailing.equalToSuperview() + } + } + + private func configureTableView() { + tableView.rowHeight = 100 + tableView.separatorStyle = .none + tableView.register(CategoryTableViewCell.self, forCellReuseIdentifier: CategoryTableViewCell.identifier) + tableView.dataSource = tableViewDatasource + tableView.delegate = tableViewHandler + tableView.reloadData() } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift new file mode 100644 index 0000000..3381cda --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -0,0 +1,51 @@ +// +// OrderListViewModel.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/17. +// + +import Foundation +import RxRelay +import RxSwift + +protocol ListViewModelAction { + var loadDetail: PublishRelay { get } +} +protocol ListViewModelState { + // TODO: - Entity Type 구체화되면 17 라인 변경 + var loadedDetail: PublishRelay { get } + var selectedProduct: PublishRelay { get } +} + +protocol ListViewModelBinding { + func action() -> ListViewModelAction + func state() -> ListViewModelState +} + +typealias ListViewModelProtocol = ListViewModelBinding + +class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModelBinding { + let list: [Product] + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + func action() -> ListViewModelAction { self } + + let loadDetail = PublishRelay() + + func state() -> ListViewModelState { self } + + let loadedDetail = PublishRelay() + let selectedProduct = PublishRelay() + + init(list: [Product]) { + self.list = list +// Observable.just(list) +// .first() +// .compactMap { $0?.first?.productCategory } +// .bind(to: selectedProduct) +// .disposed(by: disposeBag) + } +} From 207798622e03968ba01950f5316628c1027086f7 Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Tue, 17 May 2022 22:34:43 +0900 Subject: [PATCH 43/57] =?UTF-8?q?[STAR-19]=20fix:=20TableViewDatasource=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B0=80=EC=A7=80=EA=B3=A0=20=EC=9E=88?= =?UTF-8?q?=EB=8D=98=20=EB=AA=A8=EB=8D=B8=EC=9D=84=20ViewModel=EB=A1=9C=20?= =?UTF-8?q?=EC=98=AE=EA=B8=B4=20=EB=8B=A4=EC=9D=8C=20TableViewDatasource?= =?UTF-8?q?=20=EC=97=90=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrderCategoryViewController.swift | 9 +++--- .../OrderListTableViewDataSource.swift | 4 +++ .../OrderList/OrderListViewController.swift | 31 ++++++++++++++++--- .../OrderList/OrderListViewModel.swift | 21 ++++++++----- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index 95cf2a1..fa56cf8 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -99,7 +99,8 @@ class OrderCategoryViewController: UIViewController { viewModel.state().loadedList .withUnretained(self) .subscribe(onNext: { model, list in - let orderListVC = OrderListViewController(viewModel: OrderListViewModel(list: list)) + let viewModel = OrderListViewModel(list: list) + let orderListVC = OrderListViewController(viewModel: viewModel) model.navigationItem.backBarButtonItem = model.backBarButtonItem model.navigationController?.pushViewController(orderListVC, animated: true) }) @@ -166,9 +167,9 @@ class OrderCategoryViewController: UIViewController { extension OrderCategoryViewController { private func updateDatasource(menu: [Category.Group]) { self.tableViewDataSource = OrderTableViewDataSource(menus: menu) - DispatchQueue.main.async { - self.tableView.dataSource = self.tableViewDataSource - self.tableView.reloadData() + DispatchQueue.main.async { [weak self] in + self?.tableView.dataSource = self?.tableViewDataSource + self?.tableView.reloadData() } } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift index 815859f..7e9d9a3 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift @@ -13,6 +13,10 @@ class OrderListTableViewDataSource: NSObject, UITableViewDataSource { private var list: [Product] = [] + init(list: [Product]) { + self.list = list + } + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return list.count } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index 8844ab5..7853592 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -5,16 +5,18 @@ // Created by 김상혁 on 2022/05/10. // -import UIKit +import RxAppState +import RxCocoa import RxSwift import SnapKit +import UIKit class OrderListViewController: UIViewController { let tableView = UITableView() - let tableViewDatasource = OrderListTableViewDataSource() let tableViewHandler = OrderListTableViewDelegate() let viewModel: ListViewModelProtocol + var tableViewDatasource: OrderListTableViewDataSource? let categoryLabel: UILabel = { let label = UILabel() @@ -27,10 +29,9 @@ class OrderListViewController: UIViewController { init(viewModel: ListViewModelProtocol) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) + bind() attribute() - // TODO: VM 에서 list 의 첫번째 값을 categoryLabel.text 에 저장 layout() - bind() } @available(*, unavailable) @@ -39,11 +40,23 @@ class OrderListViewController: UIViewController { } private func bind() { + rx.viewDidLoad + .bind(to: viewModel.action().loadList) + .disposed(by: disposeBag) + tableViewHandler.selectedCellIndex .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) + + viewModel.state().loadedList + .withUnretained(self) + .subscribe(onNext: { model, list in + model.updateTableViewDatasource(list: list) + model.categoryLabel.text = list.first?.productCategory + }) + .disposed(by: disposeBag) } private func attribute() { @@ -77,3 +90,13 @@ class OrderListViewController: UIViewController { tableView.reloadData() } } + +extension OrderListViewController { + private func updateTableViewDatasource(list: [Product]) { + self.tableViewDatasource = OrderListTableViewDataSource(list: list) + DispatchQueue.main.async { [weak self] in + self?.tableView.dataSource = self?.tableViewDatasource + self?.tableView.reloadData() + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift index 3381cda..6124318 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -11,11 +11,12 @@ import RxSwift protocol ListViewModelAction { var loadDetail: PublishRelay { get } + var loadList: PublishRelay { get } } protocol ListViewModelState { - // TODO: - Entity Type 구체화되면 17 라인 변경 + // TODO: - Entity Type 구체화되면 loadedDetail 타입 변경 var loadedDetail: PublishRelay { get } - var selectedProduct: PublishRelay { get } + var loadedList: PublishRelay<[Product]> { get } } protocol ListViewModelBinding { @@ -34,18 +35,22 @@ class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModel func action() -> ListViewModelAction { self } let loadDetail = PublishRelay() + let loadList = PublishRelay() func state() -> ListViewModelState { self } let loadedDetail = PublishRelay() - let selectedProduct = PublishRelay() + let loadedList = PublishRelay<[Product]>() init(list: [Product]) { self.list = list -// Observable.just(list) -// .first() -// .compactMap { $0?.first?.productCategory } -// .bind(to: selectedProduct) -// .disposed(by: disposeBag) + + action().loadList + .withUnretained(self) + .compactMap { model, _ in + model.list + } + .bind(to: loadedList) + .disposed(by: disposeBag) } } From ddb446bdf8309192d27729b10d2599e7d9cc4861 Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Wed, 18 May 2022 10:31:44 +0900 Subject: [PATCH 44/57] =?UTF-8?q?[STAR-19]=20fix:=20OrderListViewModel=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Detail=20page=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B0=9B=EC=95=84=EC=98=A4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 4 ++++ .../Starbucks/Sources/API/StarbucksTarget.swift | 2 +- .../Sources/Model/DetailListEntity.swift | 8 ++++++++ .../Present/OrderCategory/OrderViewModel.swift | 2 +- .../OrderList/OrderListViewController.swift | 4 +--- .../Present/OrderList/OrderListViewModel.swift | 17 +++++++++++++++++ .../Starbucks/StarbucksRepository.swift | 2 +- .../Starbucks/StarbucksRepositoryImpl.swift | 2 +- 8 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Model/DetailListEntity.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index 1ac2306..e5edffc 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 006FA0942833408200281C4D /* OrderListTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0932833408200281C4D /* OrderListTableViewDataSource.swift */; }; 006FA0962833775400281C4D /* OrderListTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0952833775400281C4D /* OrderListTableViewDelegate.swift */; }; 006FA0982833785B00281C4D /* OrderListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0972833785B00281C4D /* OrderListViewModel.swift */; }; + 00AC77CD2833FA3B00BBBF43 /* DetailListEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00AC77CC2833FA3B00BBBF43 /* DetailListEntity.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0039B55282FD92800298719 /* WhatsNewListViewController.swift */; }; @@ -97,6 +98,7 @@ 006FA0932833408200281C4D /* OrderListTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderListTableViewDataSource.swift; sourceTree = ""; }; 006FA0952833775400281C4D /* OrderListTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderListTableViewDelegate.swift; sourceTree = ""; }; 006FA0972833785B00281C4D /* OrderListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderListViewModel.swift; sourceTree = ""; }; + 00AC77CC2833FA3B00BBBF43 /* DetailListEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailListEntity.swift; sourceTree = ""; }; 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.debug.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.debug.xcconfig"; sourceTree = ""; }; 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -466,6 +468,7 @@ E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */, E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */, 006FA0912832433A00281C4D /* CategoryProductEntity.swift */, + 00AC77CC2833FA3B00BBBF43 /* DetailListEntity.swift */, ); path = Model; sourceTree = ""; @@ -838,6 +841,7 @@ E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */, E07233B4282B7DB900AF3E16 /* CategoryTableViewCell.swift in Sources */, E07233E8282D03A000AF3E16 /* WhatsNewViewModel.swift in Sources */, + 00AC77CD2833FA3B00BBBF43 /* DetailListEntity.swift in Sources */, E07233EA282D075B00AF3E16 /* MainEventViewModel.swift in Sources */, E0108CE22831314800CC736C /* CameraSession.swift in Sources */, E07233AE282B7DB900AF3E16 /* StarbucksViewController.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift index f5718c8..e07c1f0 100644 --- a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -73,7 +73,7 @@ extension StarbucksTarget { switch self { case .requestHome: return .json - case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice, .requestCategoryProduct: + case .requestPromotion, .requestProductImage, .requestNews, .requestProductInfo, .requestNotice, .requestCategoryProduct: return .urlencode } } diff --git a/Starbucks/Starbucks/Sources/Model/DetailListEntity.swift b/Starbucks/Starbucks/Sources/Model/DetailListEntity.swift new file mode 100644 index 0000000..40fdfb1 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Model/DetailListEntity.swift @@ -0,0 +1,8 @@ +// +// DetailListEntity.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/18. +// + +import Foundation diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 920c0e8..7b15d49 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -99,7 +99,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB .compactMap { $0?.groupId } .withUnretained(self) .flatMapLatest { model, id in - model.starbucksRepository.requestCategoryProduct(id: id).asObservable() + model.starbucksRepository.requestCategoryProduct( id).asObservable() } .share() diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index 7853592..adde748 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -45,9 +45,7 @@ class OrderListViewController: UIViewController { .disposed(by: disposeBag) tableViewHandler.selectedCellIndex - .subscribe(onNext: { - print($0) - }) + .bind(to: viewModel.action().loadDetail) .disposed(by: disposeBag) viewModel.state().loadedList diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift index 6124318..59f3c59 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -52,5 +52,22 @@ class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModel } .bind(to: loadedList) .disposed(by: disposeBag) + + let requestDetailList = action().loadDetail + .withUnretained(self) + .map { model, index in + model.list[index].productCode + } + .withUnretained(self) + .flatMapLatest { model, target in + model.starbucksRepository.requestDetail(target).asObservable() + } + .share() + + requestDetailList + .bind(onNext: { + print($0.value?.view) + }) + .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift index f934c89..06f1a40 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -16,5 +16,5 @@ protocol StarbucksRepository { func requestCategory() -> Single> func requestNews() -> Single> func requestNotice() -> Single> - func requestCategoryProduct(id: String) -> Single> + func requestCategoryProduct(_ id: String) -> Single> } diff --git a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift index 97bf2b1..a1064e2 100644 --- a/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -66,7 +66,7 @@ class StarbucksRepositoryImpl: NetworkRepository, StarbucksRepo } extension StarbucksRepositoryImpl { - func requestCategoryProduct(id: String) -> Single> { + func requestCategoryProduct(_ id: String) -> Single> { provider .request(.requestCategoryProduct(id)) .map(CategoryProductEntity.self) From 9a922af063ea259827ed7afcbea611478090890e Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Wed, 18 May 2022 10:44:37 +0900 Subject: [PATCH 45/57] =?UTF-8?q?[STAR-19]=20chore:=20swiftLint=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=A0=20=EC=A4=84=EC=9D=B4=EB=8A=94=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/Home/View/HomeInfoView/HomeInfoView.swift | 2 +- .../Present/OrderCategory/OrderTableViewDataSource.swift | 6 +++--- .../Present/OrderCategory/OrderTableViewDelegate.swift | 3 +-- .../Present/OrderDetail/OrderDetailViewController.swift | 1 - .../Present/OrderList/OrderListTableViewDataSource.swift | 6 +++--- .../Sources/Present/OrderList/OrderListViewModel.swift | 2 +- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift b/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift index a510bf1..6c68ac1 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift @@ -5,8 +5,8 @@ // Created by seongha shin on 2022/05/12. // -import RxSwift import RxRelay +import RxSwift import UIKit class HomeInfoView: UIView { diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift index d70ce7e..6ddfe8d 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift @@ -5,9 +5,9 @@ // Created by 김상혁 on 2022/05/10. // -import UIKit -import RxSwift import RxRelay +import RxSwift +import UIKit class OrderTableViewDataSource: NSObject, UITableViewDataSource { @@ -19,7 +19,7 @@ class OrderTableViewDataSource: NSObject, UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return menus.count + menus.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift index bab1c0f..7cea1be 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift @@ -5,9 +5,9 @@ // Created by 김상혁 on 2022/05/10. // -import UIKit import RxRelay import RxSwift +import UIKit protocol CellSelectionDetectable: AnyObject { func didSelectCell(indexPath: IndexPath) @@ -21,6 +21,5 @@ class OrderTableViewDelegate: NSObject, UITableViewDelegate { selectedCellIndex .onNext(indexPath.row) - } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift index 03f727b..1387fa2 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift @@ -38,6 +38,5 @@ class OrderDetailViewController: UIViewController { } private func layout() { - } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift index 7e9d9a3..6a6afcb 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift @@ -5,9 +5,9 @@ // Created by 김상혁 on 2022/05/10. // -import UIKit -import RxSwift import RxRelay +import RxSwift +import UIKit class OrderListTableViewDataSource: NSObject, UITableViewDataSource { @@ -18,7 +18,7 @@ class OrderListTableViewDataSource: NSObject, UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return list.count + list.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift index 59f3c59..24a0470 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -66,7 +66,7 @@ class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModel requestDetailList .bind(onNext: { - print($0.value?.view) + print($0.value?.view as Any) }) .disposed(by: disposeBag) } From e74ba19c4070274bf41193e9715bc0ca54e4e129 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 18 May 2022 13:06:37 +0900 Subject: [PATCH 46/57] =?UTF-8?q?[STAR-25]=20feature:=20what'new=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=A0=95=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EB=94=94=ED=85=8C=EC=9D=BC=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 24 ++-- .../Camera/{ => View}/PreviewView.swift | 0 .../Common}/GradientView.swift | 0 .../Present/Common/IntrinsicTableView.swift | 21 ++++ .../Present/Common/NavigationBarView.swift | 64 ---------- .../Present/Home/HomeViewController.swift | 14 +-- .../Present/Pay/PayViewController.swift | 22 +--- .../WhatsNewListViewController.swift | 115 ++++++++++++++++-- .../Starbucks/Sources/SceneDelegate.swift | 1 - 9 files changed, 153 insertions(+), 108 deletions(-) rename Starbucks/Starbucks/Sources/Common/Camera/{ => View}/PreviewView.swift (100%) rename Starbucks/Starbucks/Sources/{Common/View => Present/Common}/GradientView.swift (100%) create mode 100644 Starbucks/Starbucks/Sources/Present/Common/IntrinsicTableView.swift delete mode 100644 Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index a373718..9ebe3b2 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -26,7 +26,7 @@ E0108CF42833658200CC736C /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CF32833658200CC736C /* UserDefault.swift */; }; E0108CF6283365CE00CC736C /* UserStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CF5283365CE00CC736C /* UserStore.swift */; }; E0108CF82833784800CC736C /* CardListViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CF72833784800CC736C /* CardListViewDataSource.swift */; }; - E0108CFB28338D3A00CC736C /* NavigationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CFA28338D3A00CC736C /* NavigationBarView.swift */; }; + E0108CFF2834A35200CC736C /* IntrinsicTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0108CFE2834A35200CC736C /* IntrinsicTableView.swift */; }; E01B528328289D18009918AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E01B528228289D18009918AE /* Assets.xcassets */; }; E0723058282A13C900AF3E16 /* StarbuckTargetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */; }; E07233AA282B7DB900AF3E16 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0723381282B7DB900AF3E16 /* HomeViewModel.swift */; }; @@ -111,7 +111,7 @@ E0108CF32833658200CC736C /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = ""; }; E0108CF5283365CE00CC736C /* UserStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStore.swift; sourceTree = ""; }; E0108CF72833784800CC736C /* CardListViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardListViewDataSource.swift; sourceTree = ""; }; - E0108CFA28338D3A00CC736C /* NavigationBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarView.swift; sourceTree = ""; }; + E0108CFE2834A35200CC736C /* IntrinsicTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntrinsicTableView.swift; sourceTree = ""; }; E01B527628289D17009918AE /* Starbucks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Starbucks.app; sourceTree = BUILT_PRODUCTS_DIR; }; E01B528228289D18009918AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E01B528728289D18009918AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -229,9 +229,9 @@ E0108CE02831310E00CC736C /* Camera */ = { isa = PBXGroup; children = ( + E0108CFC2834A32900CC736C /* View */, E0108CE12831314800CC736C /* CameraSession.swift */, E0108CE32831E8D600CC736C /* CameraSessionError.swift */, - E0108CE7283260C200CC736C /* PreviewView.swift */, ); path = Camera; sourceTree = ""; @@ -255,10 +255,19 @@ path = CardList; sourceTree = ""; }; - E0108CF928338D2800CC736C /* Common */ = { + E0108CFC2834A32900CC736C /* View */ = { + isa = PBXGroup; + children = ( + E0108CE7283260C200CC736C /* PreviewView.swift */, + ); + path = View; + sourceTree = ""; + }; + E0108CFD2834A33C00CC736C /* Common */ = { isa = PBXGroup; children = ( - E0108CFA28338D3A00CC736C /* NavigationBarView.swift */, + E07233F7282D516200AF3E16 /* GradientView.swift */, + E0108CFE2834A35200CC736C /* IntrinsicTableView.swift */, ); path = Common; sourceTree = ""; @@ -337,7 +346,6 @@ E072337F282B7DB900AF3E16 /* Present */ = { isa = PBXGroup; children = ( - E0108CF928338D2800CC736C /* Common */, E0723386282B7DB900AF3E16 /* TabBar */, E0723380282B7DB900AF3E16 /* Home */, E0039B54282FD8C000298719 /* WhatsNewList */, @@ -345,6 +353,7 @@ E0723388282B7DB900AF3E16 /* OrderDetail */, E072338A282B7DB900AF3E16 /* OrderList */, E072338C282B7DB900AF3E16 /* OrderCategory */, + E0108CFD2834A33C00CC736C /* Common */, E0723383282B7DB900AF3E16 /* RootWindow.swift */, ); path = Present; @@ -542,7 +551,6 @@ E07233F6282D515800AF3E16 /* View */ = { isa = PBXGroup; children = ( - E07233F7282D516200AF3E16 /* GradientView.swift */, ); path = View; sourceTree = ""; @@ -769,6 +777,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E0108CFF2834A35200CC736C /* IntrinsicTableView.swift in Sources */, E07233E3282CA71B00AF3E16 /* MainEventViewController.swift in Sources */, E07233C2282B7DB900AF3E16 /* BaseTarget.swift in Sources */, E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */, @@ -795,7 +804,6 @@ E07233DE282BDDB200AF3E16 /* RecommandMenuDataSource.swift in Sources */, E07233F8282D516200AF3E16 /* GradientView.swift in Sources */, E07233C3282B7DB900AF3E16 /* HTTPMethod.swift in Sources */, - E0108CFB28338D3A00CC736C /* NavigationBarView.swift in Sources */, E07233C6282B7DB900AF3E16 /* SceneDelegate.swift in Sources */, E07233C0282B7DB900AF3E16 /* HTTPContentType.swift in Sources */, E07233AC282B7DB900AF3E16 /* RootWindow.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/Common/Camera/PreviewView.swift b/Starbucks/Starbucks/Sources/Common/Camera/View/PreviewView.swift similarity index 100% rename from Starbucks/Starbucks/Sources/Common/Camera/PreviewView.swift rename to Starbucks/Starbucks/Sources/Common/Camera/View/PreviewView.swift diff --git a/Starbucks/Starbucks/Sources/Common/View/GradientView.swift b/Starbucks/Starbucks/Sources/Present/Common/GradientView.swift similarity index 100% rename from Starbucks/Starbucks/Sources/Common/View/GradientView.swift rename to Starbucks/Starbucks/Sources/Present/Common/GradientView.swift diff --git a/Starbucks/Starbucks/Sources/Present/Common/IntrinsicTableView.swift b/Starbucks/Starbucks/Sources/Present/Common/IntrinsicTableView.swift new file mode 100644 index 0000000..d494a4a --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Common/IntrinsicTableView.swift @@ -0,0 +1,21 @@ +// +// IntrinsicTableView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/18. +// + +import UIKit + +final class IntrinsicTableView: UITableView { + + override var intrinsicContentSize: CGSize { + let height = self.contentSize.height + self.contentInset.top + self.contentInset.bottom + return CGSize(width: self.contentSize.width, height: height) + } + + override func layoutSubviews() { + self.invalidateIntrinsicContentSize() + super.layoutSubviews() + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift b/Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift deleted file mode 100644 index 5c6c04d..0000000 --- a/Starbucks/Starbucks/Sources/Present/Common/NavigationBarView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// NavigationBarView.swift -// Starbucks -// -// Created by seongha shin on 2022/05/17. -// - -import UIKit - -class NavigationBarView: UIView { - - private let navigationView = UIView() - - private let titleLabel: UILabel = { - let label = UILabel() - label.textAlignment = .center - label.font = .systemFont(ofSize: 24, weight: .bold) - label.textColor = .black - label.alpha = 0 - return label - }() - - var title: String = "" { - didSet { - titleLabel.text = title - } - } - - override init(frame: CGRect) { - super.init(frame: .zero) - attribute() - layout() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("Init with coder is unavailable") - } - - private func attribute() { - backgroundColor = .white - } - - private func layout() { - let topSafeAreaInset = UIApplication.shared.windows[0].safeAreaInsets.top - - addSubview(navigationView) - navigationView.addSubview(titleLabel) - - navigationView.snp.makeConstraints { - $0.top.equalToSuperview().offset(topSafeAreaInset) - $0.leading.trailing.equalToSuperview() - $0.height.equalTo(topSafeAreaInset) - } - - titleLabel.snp.makeConstraints { - $0.edges.equalToSuperview() - } - } - - func setAlpha(_ alpha: CGFloat) { - titleLabel.alpha = alpha - } -} diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 5add8dc..168a47c 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -58,12 +58,6 @@ class HomeViewController: UIViewController { return viewController }() - private let backButton: UIBarButtonItem = { - let barButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) - barButton.tintColor = .black - return barButton - }() - private let viewModel: HomeViewModelProtocol private let disposeBag = DisposeBag() private var topSafeArea: CGFloat = 0 @@ -90,9 +84,10 @@ class HomeViewController: UIViewController { .map { _ in } .withUnretained(self) .bind(onNext: { vc, _ in - vc.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) - vc.navigationController?.navigationBar.shadowImage = UIImage() - vc.navigationController?.navigationBar.backgroundColor = .clear + vc.navigationController?.navigationBar.isHidden = true +// vc.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) +// vc.navigationController?.navigationBar.shadowImage = UIImage() +// vc.navigationController?.navigationBar.backgroundColor = .clear }) .disposed(by: disposeBag) @@ -124,7 +119,6 @@ class HomeViewController: UIViewController { ) .withUnretained(self) .bind(onNext: { vc, _ in - vc.navigationItem.backBarButtonItem = vc.backButton let viewController = WhatsNewListViewController(viewModel: WhatsNewListViewModel()) vc.navigationController?.pushViewController(viewController, animated: true) }) diff --git a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift index 0124b67..8a469fe 100644 --- a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift @@ -11,12 +11,6 @@ import UIKit class PayViewController: UIViewController { - private let customNavigationBarView: NavigationBarView = { - let view = NavigationBarView() - view.title = "Pay" - return view - }() - private let scrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.isPagingEnabled = false @@ -89,9 +83,11 @@ class PayViewController: UIViewController { .map { _ in } .withUnretained(self) .bind(onNext: { vc, _ in - vc.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) - vc.navigationController?.navigationBar.shadowImage = UIImage() - vc.navigationController?.navigationBar.backgroundColor = .clear + let titleColor: UIColor = .black.withAlphaComponent(0) + vc.title = "Pay" + vc.navigationController?.navigationBar.barTintColor = .white + vc.navigationController?.navigationBar.isTranslucent = false + vc.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: titleColor] vc.navigationItem.rightBarButtonItem = vc.tappedAddCard }) .disposed(by: disposeBag) @@ -134,7 +130,6 @@ class PayViewController: UIViewController { private func layout() { view.addSubview(scrollView) - view.addSubview(customNavigationBarView) view.addSubview(chargeView) scrollView.addSubview(contentStackView) @@ -144,11 +139,6 @@ class PayViewController: UIViewController { chargeView.addSubview(previewView) chargeView.addSubview(captureButton) - customNavigationBarView.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() - $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.top) - } - scrollView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) $0.leading.trailing.bottom.equalToSuperview() @@ -199,6 +189,6 @@ extension PayViewController: UIScrollViewDelegate { alpha = alpha > 1 ? 1 : alpha } - customNavigationBarView.setAlpha(alpha) + navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.black.withAlphaComponent(alpha)] } } diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift index 60d35a1..fc885b2 100644 --- a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift @@ -9,6 +9,9 @@ import RxSwift import UIKit class WhatsNewListViewController: UIViewController { + enum Constants { + static let navigationHeight = 50.0 + } private let navigationView: UIView = { let view = UIView() @@ -16,13 +19,46 @@ class WhatsNewListViewController: UIViewController { return view }() - private let tableView: UITableView = { - let tableView = UITableView() + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "What's New" + label.font = .systemFont(ofSize: 18, weight: .bold) + label.alpha = 0 + return label + }() + + private let backButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "chevron.backward"), for: .normal) + button.imageView?.tintColor = .black + return button + }() + + private let scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.isPagingEnabled = false + scrollView.showsHorizontalScrollIndicator = true + scrollView.contentInsetAdjustmentBehavior = .never + return scrollView + }() + + private let scrollContentView = UIView() + + private let tableView: IntrinsicTableView = { + let tableView = IntrinsicTableView() tableView.register(WhatsNewListViewCell.self, forCellReuseIdentifier: WhatsNewListViewCell.identifier) tableView.estimatedRowHeight = 50 + tableView.isScrollEnabled = false return tableView }() + private let tableTitleLabel: UILabel = { + let label = UILabel() + label.text = "What's New" + label.font = .systemFont(ofSize: 23, weight: .bold) + return label + }() + private let viewModel: WhatsNewListViewProtocol private let disposeBag = DisposeBag() private let dataSource = WhatsNewListDataSource() @@ -40,8 +76,11 @@ class WhatsNewListViewController: UIViewController { fatalError("init(coder:) has not been implemented") } + deinit { + Log.info("Deinit WhatsNewListViewController") + } + private func bind() { - rx.viewDidLoad .bind(to: viewModel.action().loadEvents) .disposed(by: disposeBag) @@ -53,27 +92,85 @@ class WhatsNewListViewController: UIViewController { viewModel.state().reloadEvents .bind(onNext: tableView.reloadData) .disposed(by: disposeBag) + + backButton.rx.tap + .withUnretained(self) + .bind(onNext: { vc, _ in + vc.navigationController?.popViewController(animated: true) + }) + .disposed(by: disposeBag) } private func attribute() { - hidesBottomBarWhenPushed = true view.backgroundColor = .white - title = "What's New" - + scrollView.delegate = self tableView.dataSource = dataSource } private func layout() { - view.addSubview(tableView) view.addSubview(navigationView) + navigationView.addSubview(titleLabel) + navigationView.addSubview(backButton) + + view.addSubview(scrollView) + scrollView.addSubview(scrollContentView) + + scrollContentView.addSubview(tableTitleLabel) + scrollContentView.addSubview(tableView) navigationView.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(Constants.navigationHeight) + } + + titleLabel.snp.makeConstraints { + $0.top.bottom.centerX.equalToSuperview() + } + + backButton.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.centerY.equalToSuperview() + $0.width.height.equalTo(navigationView.snp.height) + } + + scrollView.snp.makeConstraints { + $0.top.equalTo(navigationView.snp.bottom) + $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) + $0.leading.trailing.equalToSuperview() + } + + scrollView.contentLayoutGuide.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.bottom.equalTo(scrollContentView) + } + + scrollContentView.snp.makeConstraints { $0.top.leading.trailing.equalToSuperview() - $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.top) + $0.bottom.equalTo(tableView) + } + + tableTitleLabel.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview().inset(20) } tableView.snp.makeConstraints { - $0.edges.equalToSuperview() + $0.top.equalTo(tableTitleLabel.snp.bottom) + $0.leading.trailing.equalToSuperview() } } } + +extension WhatsNewListViewController: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + var alpha: CGFloat = 0 + if scrollView.contentOffset.y < 0 { + alpha = 0 + } else { + alpha = scrollView.contentOffset.y / titleLabel.frame.size.height + alpha = alpha > 1 ? 1 : alpha + } + + titleLabel.alpha = alpha + } +} diff --git a/Starbucks/Starbucks/Sources/SceneDelegate.swift b/Starbucks/Starbucks/Sources/SceneDelegate.swift index c8af78b..12ec3a2 100644 --- a/Starbucks/Starbucks/Sources/SceneDelegate.swift +++ b/Starbucks/Starbucks/Sources/SceneDelegate.swift @@ -12,7 +12,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let scene = (scene as? UIWindowScene) else { return } - self.window = RootWindow(windowScene: scene) window?.makeKeyAndVisible() } From 4f445bd270b6a4c27bbcea88c8f7ee18e9af78f7 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 18 May 2022 14:55:12 +0900 Subject: [PATCH 47/57] =?UTF-8?q?[STAR-25]=20feature:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=ED=97=A4=EB=8D=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WhatsNewListViewController.swift | 55 ++++++------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift index fc885b2..2f04236 100644 --- a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift @@ -11,6 +11,7 @@ import UIKit class WhatsNewListViewController: UIViewController { enum Constants { static let navigationHeight = 50.0 + static let tableHeaderViewHeight = 80.0 } private let navigationView: UIView = { @@ -34,21 +35,12 @@ class WhatsNewListViewController: UIViewController { return button }() - private let scrollView: UIScrollView = { - let scrollView = UIScrollView() - scrollView.isPagingEnabled = false - scrollView.showsHorizontalScrollIndicator = true - scrollView.contentInsetAdjustmentBehavior = .never - return scrollView - }() - - private let scrollContentView = UIView() - - private let tableView: IntrinsicTableView = { - let tableView = IntrinsicTableView() + private let tableHeaderView = UIView() + + private let tableView: UITableView = { + let tableView = UITableView() tableView.register(WhatsNewListViewCell.self, forCellReuseIdentifier: WhatsNewListViewCell.identifier) tableView.estimatedRowHeight = 50 - tableView.isScrollEnabled = false return tableView }() @@ -103,8 +95,9 @@ class WhatsNewListViewController: UIViewController { private func attribute() { view.backgroundColor = .white - scrollView.delegate = self tableView.dataSource = dataSource + tableView.delegate = self + tableView.tableHeaderView = tableHeaderView } private func layout() { @@ -112,11 +105,8 @@ class WhatsNewListViewController: UIViewController { navigationView.addSubview(titleLabel) navigationView.addSubview(backButton) - view.addSubview(scrollView) - scrollView.addSubview(scrollContentView) - - scrollContentView.addSubview(tableTitleLabel) - scrollContentView.addSubview(tableView) + view.addSubview(tableView) + tableHeaderView.addSubview(tableTitleLabel) navigationView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) @@ -134,40 +124,31 @@ class WhatsNewListViewController: UIViewController { $0.width.height.equalTo(navigationView.snp.height) } - scrollView.snp.makeConstraints { - $0.top.equalTo(navigationView.snp.bottom) - $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) - $0.leading.trailing.equalToSuperview() - } - - scrollView.contentLayoutGuide.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() - $0.bottom.equalTo(scrollContentView) - } - - scrollContentView.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() - $0.bottom.equalTo(tableView) + tableHeaderView.snp.makeConstraints { + $0.height.equalTo(Constants.tableHeaderViewHeight) + $0.width.equalToSuperview() } tableTitleLabel.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview().inset(20) + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(20) } tableView.snp.makeConstraints { - $0.top.equalTo(tableTitleLabel.snp.bottom) + $0.top.equalTo(navigationView.snp.bottom) $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(view.safeAreaLayoutGuide) } } } -extension WhatsNewListViewController: UIScrollViewDelegate { +extension WhatsNewListViewController: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { var alpha: CGFloat = 0 if scrollView.contentOffset.y < 0 { alpha = 0 } else { - alpha = scrollView.contentOffset.y / titleLabel.frame.size.height + alpha = scrollView.contentOffset.y / tableHeaderView.frame.height alpha = alpha > 1 ? 1 : alpha } From 22a69191fe991f75b92fbe66ac3989c5468a335d Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Wed, 18 May 2022 16:02:32 +0900 Subject: [PATCH 48/57] =?UTF-8?q?[STAR-26]=20refactor:=20OrderListViewCont?= =?UTF-8?q?roller=EA=B0=80=20productCode=EB=A7=8C=20=EC=A3=BC=EC=9E=85?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=84=9C=20=EC=9E=90=EC=8B=A0=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20API=EB=A5=BC=20=EC=8A=A4=EC=8A=A4=EB=A1=9C?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=ED=95=98=EB=8A=94=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Model/StarbucksEntity.swift | 26 ++++++++++ .../OrderCategoryViewController.swift | 20 ++++---- .../OrderCategory/OrderViewModel.swift | 25 +++------- .../OrderListTableViewDataSource.swift | 14 +++--- .../OrderList/OrderListViewController.swift | 26 ++++------ .../OrderList/OrderListViewModel.swift | 47 +++++++++---------- 6 files changed, 79 insertions(+), 79 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift index dc8db49..44552d9 100644 --- a/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -96,9 +96,35 @@ extension StarbucksEntity { struct ProductInfo: Decodable { let productName: String + let productEnglishName: String + let productCode: String + let recommendDescription: String + let hotAvailable: String + let kilocalorie: String + let fat: String + let sturatedFat: String + let transFat: String + let cholesterol: String + let sugars: String + let protein: String + let caffeine: String + let allergy: String enum CodingKeys: String, CodingKey { case productName = "product_NM" + case productEnglishName = "product_ENGNM" + case productCode = "product_CD" + case recommendDescription = "recommend" + case hotAvailable = "hot_YN" + case kilocalorie = "kcal" + case fat + case sturatedFat = "sat_FAT" + case transFat = "trans_FAT" + case cholesterol + case sugars + case protein + case caffeine + case allergy } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index fa56cf8..0e6e36b 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -96,16 +96,6 @@ class OrderCategoryViewController: UIViewController { }) .disposed(by: disposeBag) - viewModel.state().loadedList - .withUnretained(self) - .subscribe(onNext: { model, list in - let viewModel = OrderListViewModel(list: list) - let orderListVC = OrderListViewController(viewModel: viewModel) - model.navigationItem.backBarButtonItem = model.backBarButtonItem - model.navigationController?.pushViewController(orderListVC, animated: true) - }) - .disposed(by: disposeBag) - categoryButtons.enumerated().forEach { index, button in button.rx.tap .compactMap { _ in @@ -114,6 +104,16 @@ class OrderCategoryViewController: UIViewController { .bind(to: viewModel.action().tappedCategory) .disposed(by: disposeBag) } + + viewModel.state().selectedProductCode + .withUnretained(self) + .bind(onNext: { model, productCode in + let viewModel = OrderListViewModel(productCode: productCode) + let orderListVC = OrderListViewController(viewModel: viewModel) + model.navigationItem.backBarButtonItem = model.backBarButtonItem + model.navigationController?.pushViewController(orderListVC, animated: true) + }) + .disposed(by: disposeBag) } private func attribute() { diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 7b15d49..894bd29 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -18,8 +18,7 @@ protocol OrderViewModelAction { protocol OrderViewModelState { var loadedCategory: PublishRelay<[Category.Group]> { get } var selectedCategory: PublishRelay { get } - var selectedMenu: PublishRelay { get } - var loadedList: PublishRelay<[Product]> { get } + var selectedProductCode: PublishRelay { get } } protocol OrderViewModelBinding { @@ -41,8 +40,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB let loadedCategory = PublishRelay<[Category.Group]>() let selectedCategory = PublishRelay() - let selectedMenu = PublishRelay() - let loadedList = PublishRelay<[Product]>() + let selectedProductCode = PublishRelay() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository @@ -91,22 +89,13 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB .compactMap { model, type in model.categoryMenu[type] } //선택한 카테고리 데이터 반환 .bind(to: loadedCategory) //선택한 카테고리 데이터 전달 .disposed(by: disposeBag) - - let requestProducts = action().tapMenu + + action().tapMenu .withLatestFrom(tappedCategory) { [weak self] in - self?.categoryMenu[$1]?[$0] - } - .compactMap { $0?.groupId } - .withUnretained(self) - .flatMapLatest { model, id in - model.starbucksRepository.requestCategoryProduct( id).asObservable() + self?.categoryMenu[$1]?[$0].groupId } - .share() - - requestProducts - .compactMap { $0.value } - .map { $0.products } - .bind(to: loadedList) + .compactMap { $0 } + .bind(to: selectedProductCode) .disposed(by: disposeBag) } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift index 6a6afcb..d26a90c 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift @@ -11,14 +11,10 @@ import UIKit class OrderListTableViewDataSource: NSObject, UITableViewDataSource { - private var list: [Product] = [] - - init(list: [Product]) { - self.list = list - } + private var products: [Product] = [] func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - list.count + products.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -26,10 +22,14 @@ class OrderListTableViewDataSource: NSObject, UITableViewDataSource { as? CategoryTableViewCell else { return UITableViewCell() } - let indexCell = list[indexPath.row] + let indexCell = products[indexPath.row] cell.setMenuName(text: indexCell.productName) // cell.setSubName(text: <#T##String#>) cell.setThumbnail(url: indexCell.completeUrl) return cell } + + func update(products: [Product]) { + self.products = products + } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index adde748..af8cabd 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -16,9 +16,9 @@ class OrderListViewController: UIViewController { let tableView = UITableView() let tableViewHandler = OrderListTableViewDelegate() let viewModel: ListViewModelProtocol - var tableViewDatasource: OrderListTableViewDataSource? + var tableViewDatasource = OrderListTableViewDataSource() - let categoryLabel: UILabel = { + private let categoryLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 28, weight: .bold) return label @@ -48,12 +48,12 @@ class OrderListViewController: UIViewController { .bind(to: viewModel.action().loadDetail) .disposed(by: disposeBag) - viewModel.state().loadedList - .withUnretained(self) - .subscribe(onNext: { model, list in - model.updateTableViewDatasource(list: list) - model.categoryLabel.text = list.first?.productCategory - }) + viewModel.state().updatedList + .bind(onNext: tableViewDatasource.update) + .disposed(by: disposeBag) + + viewModel.state().reloadedList + .bind(onNext: tableView.reloadData) .disposed(by: disposeBag) } @@ -88,13 +88,3 @@ class OrderListViewController: UIViewController { tableView.reloadData() } } - -extension OrderListViewController { - private func updateTableViewDatasource(list: [Product]) { - self.tableViewDatasource = OrderListTableViewDataSource(list: list) - DispatchQueue.main.async { [weak self] in - self?.tableView.dataSource = self?.tableViewDatasource - self?.tableView.reloadData() - } - } -} diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift index 24a0470..78265ff 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -13,10 +13,12 @@ protocol ListViewModelAction { var loadDetail: PublishRelay { get } var loadList: PublishRelay { get } } + protocol ListViewModelState { - // TODO: - Entity Type 구체화되면 loadedDetail 타입 변경 - var loadedDetail: PublishRelay { get } - var loadedList: PublishRelay<[Product]> { get } + var loadedDetail: PublishRelay { get } + var loadedDetailImage: PublishRelay { get } + var updatedList: PublishRelay<[Product]> { get } + var reloadedList: PublishRelay { get } } protocol ListViewModelBinding { @@ -27,7 +29,6 @@ protocol ListViewModelBinding { typealias ListViewModelProtocol = ListViewModelBinding class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModelBinding { - let list: [Product] @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() @@ -39,35 +40,29 @@ class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModel func state() -> ListViewModelState { self } - let loadedDetail = PublishRelay() + var loadedDetail = PublishRelay() + var loadedDetailImage = PublishRelay() let loadedList = PublishRelay<[Product]>() + let updatedList = PublishRelay<[Product]>() + let reloadedList = PublishRelay() - init(list: [Product]) { - self.list = list - - action().loadList - .withUnretained(self) - .compactMap { model, _ in - model.list - } - .bind(to: loadedList) - .disposed(by: disposeBag) + init(productCode: String) { - let requestDetailList = action().loadDetail + let requestProducts = action().loadList .withUnretained(self) - .map { model, index in - model.list[index].productCode + .flatMapLatest { model, _ in + model.starbucksRepository.requestCategoryProduct(productCode).asObservable() } + .share() + + requestProducts + .compactMap { $0.value?.products } .withUnretained(self) - .flatMapLatest { model, target in - model.starbucksRepository.requestDetail(target).asObservable() + .do { model, products in + model.updatedList.accept(products) } - .share() - - requestDetailList - .bind(onNext: { - print($0.value?.view as Any) - }) + .map { _ in } + .bind(to: reloadedList) .disposed(by: disposeBag) } } From 05efaae9e395b28cabaa670230f0b0483ca83303 Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Wed, 18 May 2022 16:06:34 +0900 Subject: [PATCH 49/57] =?UTF-8?q?[STAR-26]=20refactor:=20OrderListViewCont?= =?UTF-8?q?roller=20=EC=A0=91=EA=B7=BC=EC=A0=9C=EC=96=B4=EC=9E=90=20privat?= =?UTF-8?q?e=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Present/OrderList/OrderListViewController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index af8cabd..4e2d74a 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -13,10 +13,10 @@ import UIKit class OrderListViewController: UIViewController { - let tableView = UITableView() - let tableViewHandler = OrderListTableViewDelegate() - let viewModel: ListViewModelProtocol - var tableViewDatasource = OrderListTableViewDataSource() + private let tableView = UITableView() + private let tableViewHandler = OrderListTableViewDelegate() + private let viewModel: ListViewModelProtocol + private var tableViewDatasource = OrderListTableViewDataSource() private let categoryLabel: UILabel = { let label = UILabel() From 0cb627985194793ef3566b14b6e4ebdc05825f5d Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Wed, 18 May 2022 18:09:34 +0900 Subject: [PATCH 50/57] =?UTF-8?q?[STAR-26]=20refactor:=20OrderListVM=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=EC=97=90=20subCategory=EC=9D=98=20g?= =?UTF-8?q?ruopId=EC=99=80=20title=EC=9D=84=20=EB=84=98=EA=B2=A8=EC=A3=BC?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrderCategory/OrderCategoryViewController.swift | 6 +++--- .../Sources/Present/OrderCategory/OrderViewModel.swift | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index 0e6e36b..18c216c 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -105,10 +105,10 @@ class OrderCategoryViewController: UIViewController { .disposed(by: disposeBag) } - viewModel.state().selectedProductCode + viewModel.state().selectedSubCategory .withUnretained(self) - .bind(onNext: { model, productCode in - let viewModel = OrderListViewModel(productCode: productCode) + .bind(onNext: { model, subCategory in + let viewModel = OrderListViewModel(subCategory: subCategory.groupId, title: subCategory.title) let orderListVC = OrderListViewController(viewModel: viewModel) model.navigationItem.backBarButtonItem = model.backBarButtonItem model.navigationController?.pushViewController(orderListVC, animated: true) diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index 894bd29..acc801e 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -18,7 +18,7 @@ protocol OrderViewModelAction { protocol OrderViewModelState { var loadedCategory: PublishRelay<[Category.Group]> { get } var selectedCategory: PublishRelay { get } - var selectedProductCode: PublishRelay { get } + var selectedSubCategory: PublishRelay { get } } protocol OrderViewModelBinding { @@ -40,7 +40,7 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB let loadedCategory = PublishRelay<[Category.Group]>() let selectedCategory = PublishRelay() - let selectedProductCode = PublishRelay() + let selectedSubCategory = PublishRelay() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository @@ -92,10 +92,10 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB action().tapMenu .withLatestFrom(tappedCategory) { [weak self] in - self?.categoryMenu[$1]?[$0].groupId + self?.categoryMenu[$1]?[$0] } .compactMap { $0 } - .bind(to: selectedProductCode) + .bind(to: selectedSubCategory) .disposed(by: disposeBag) } } From f6a68c863ee794ed0932a420f2f9a271c6589fed Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Wed, 18 May 2022 18:10:20 +0900 Subject: [PATCH 51/57] =?UTF-8?q?[STAR-26]=20fix:=20categoryLabel=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrderList/OrderListViewController.swift | 9 +++++++++ .../Present/OrderList/OrderListViewModel.swift | 14 +++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index 4e2d74a..cd21dd9 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -40,10 +40,19 @@ class OrderListViewController: UIViewController { } private func bind() { + rx.viewDidLoad .bind(to: viewModel.action().loadList) .disposed(by: disposeBag) + rx.viewDidLoad + .bind(to: viewModel.action().updateTitle) + .disposed(by: disposeBag) + + viewModel.state().updatedTitle + .bind(to: categoryLabel.rx.text) + .disposed(by: disposeBag) + tableViewHandler.selectedCellIndex .bind(to: viewModel.action().loadDetail) .disposed(by: disposeBag) diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift index 78265ff..0bfc146 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -12,6 +12,7 @@ import RxSwift protocol ListViewModelAction { var loadDetail: PublishRelay { get } var loadList: PublishRelay { get } + var updateTitle: PublishRelay { get } } protocol ListViewModelState { @@ -19,6 +20,7 @@ protocol ListViewModelState { var loadedDetailImage: PublishRelay { get } var updatedList: PublishRelay<[Product]> { get } var reloadedList: PublishRelay { get } + var updatedTitle: PublishRelay { get } } protocol ListViewModelBinding { @@ -37,21 +39,27 @@ class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModel let loadDetail = PublishRelay() let loadList = PublishRelay() + let updateTitle = PublishRelay() func state() -> ListViewModelState { self } var loadedDetail = PublishRelay() var loadedDetailImage = PublishRelay() - let loadedList = PublishRelay<[Product]>() let updatedList = PublishRelay<[Product]>() let reloadedList = PublishRelay() + let updatedTitle = PublishRelay() - init(productCode: String) { + init(subCategory: String, title: String) { + + action().updateTitle + .map { title } + .bind(to: state().updatedTitle) + .disposed(by: disposeBag) let requestProducts = action().loadList .withUnretained(self) .flatMapLatest { model, _ in - model.starbucksRepository.requestCategoryProduct(productCode).asObservable() + model.starbucksRepository.requestCategoryProduct(subCategory).asObservable() } .share() From 75f94f408c4c0959534f4c6d4b3f1ad04e303660 Mon Sep 17 00:00:00 2001 From: shingha Date: Wed, 18 May 2022 20:17:22 +0900 Subject: [PATCH 52/57] =?UTF-8?q?Readme=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7630ab2..040fc8b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -## StackBucks App +## 2022 StackBucks App + +------ ### ProjectSetting @@ -6,11 +8,13 @@ - UI: Code Base + SnapKit - Architecture: MVVM + RxSwift +### Use Pods + +* SwiftLint, SnapKit, Alamofire, RxSwift, RxCocoa, RxAppState, Quick, Nimble + ### Developer -* Shingha - [Git](https://github.com/shingha1124) -* Gucci - [Git](https://github.com/Damagucci-Juice) -* Mase - [Git](https://github.com/sanghyeok-kim) +* [Shingha](https://github.com/shingha1124), [Gucci](https://github.com/Damagucci-Juice), [Mase](https://github.com/sanghyeok-kim) ### Issue @@ -20,3 +24,49 @@ * [Wiki](https://github.com/shingha1124/swift-starbucks/wiki) +------ + +# 앱 화면 + +------ + +## Home + +![홈화면](https://user-images.githubusercontent.com/5019378/169022684-7368e721-cfdb-4369-84bc-b552fcfd6be0.gif) + +* ### 스토리 + + * 사용자의 추천 메뉴를 보여준다 + * 좌우로 스크롤하여 확인 할 수 있다 + * 메인 이벤트를 확인 할 수 있다 + * What's New를 확인 할 수 있다 + * 상단의 What's New, See all 버튼을 터치하여 이벤트 페이지로 이동 할 수 있다 + * 현재 시간의 추천메뉴를 확인 할 수 있다 + * 좌우로 스크롤하여 확인 할 수 있다 + +------ + +## What' New + +![이벤트](https://user-images.githubusercontent.com/5019378/169025302-80fd6e46-2f1a-4669-bb39-b27b09db951a.gif) + +* ### 스토리 + + * 홈에서 What's New 버튼을 눌러 입장할 수 있다 + * 현재 등록된 이벤트 및 공지사항을 확인 할 수 있다 + +------ + +## Pay + +![페이](https://user-images.githubusercontent.com/5019378/169026542-5393f65d-43f6-45e3-b92b-0a6c767a2a16.gif) + +* ### 스토리 + + * 등록된 카드를 좌우로 넘기며 확인 할 수 있다 + + * 충전된 금액을 확인 할 수 있다 + + * 머신러닝을 이용하여 현금을 촬영하여 금액을 충전 할 수 있다 + + From 35acab0200359398c15cd9ffe22ed89124547264 Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Wed, 18 May 2022 23:15:36 +0900 Subject: [PATCH 53/57] =?UTF-8?q?[STAR-26]=20rename:=20ListViewModelProtoc?= =?UTF-8?q?ol=20->=20OrderListViewModelProtocol=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=86=A0=EC=BD=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrderList/OrderListViewController.swift | 4 ++-- .../Present/OrderList/OrderListViewModel.swift | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index cd21dd9..cdc44a2 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -15,7 +15,7 @@ class OrderListViewController: UIViewController { private let tableView = UITableView() private let tableViewHandler = OrderListTableViewDelegate() - private let viewModel: ListViewModelProtocol + private let viewModel: OrderListViewModelProtocol private var tableViewDatasource = OrderListTableViewDataSource() private let categoryLabel: UILabel = { @@ -26,7 +26,7 @@ class OrderListViewController: UIViewController { private let disposeBag = DisposeBag() - init(viewModel: ListViewModelProtocol) { + init(viewModel: OrderListViewModelProtocol) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) bind() diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift index 0bfc146..29ed0f8 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -9,13 +9,13 @@ import Foundation import RxRelay import RxSwift -protocol ListViewModelAction { +protocol OrderListViewModelAction { var loadDetail: PublishRelay { get } var loadList: PublishRelay { get } var updateTitle: PublishRelay { get } } -protocol ListViewModelState { +protocol OrderListViewModelState { var loadedDetail: PublishRelay { get } var loadedDetailImage: PublishRelay { get } var updatedList: PublishRelay<[Product]> { get } @@ -23,25 +23,25 @@ protocol ListViewModelState { var updatedTitle: PublishRelay { get } } -protocol ListViewModelBinding { - func action() -> ListViewModelAction - func state() -> ListViewModelState +protocol OrderListViewModelBinding { + func action() -> OrderListViewModelAction + func state() -> OrderListViewModelState } -typealias ListViewModelProtocol = ListViewModelBinding +typealias OrderListViewModelProtocol = OrderListViewModelBinding -class OrderListViewModel: ListViewModelAction, ListViewModelState, ListViewModelBinding { +class OrderListViewModel: OrderListViewModelAction, OrderListViewModelState, OrderListViewModelBinding { @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() - func action() -> ListViewModelAction { self } + func action() -> OrderListViewModelAction { self } let loadDetail = PublishRelay() let loadList = PublishRelay() let updateTitle = PublishRelay() - func state() -> ListViewModelState { self } + func state() -> OrderListViewModelState { self } var loadedDetail = PublishRelay() var loadedDetailImage = PublishRelay() From 549a509a05546fd80b9c67d5526c80f6e1d2ebcc Mon Sep 17 00:00:00 2001 From: sanghyeok-kim Date: Thu, 19 May 2022 12:05:08 +0900 Subject: [PATCH 54/57] =?UTF-8?q?[STAR-27]=20feat:=20OrderDetailViewContro?= =?UTF-8?q?ller=EC=97=90=EA=B2=8C=20productCode=EB=A5=BC=20=EB=84=98?= =?UTF-8?q?=EA=B2=A8=EC=A3=BC=EA=B3=A0=20Push=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EA=B9=8C=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 4 ++ .../Present/Home/HomeViewController.swift | 11 ++++-- .../OrderDetailViewController.swift | 6 ++- .../OrderDetail/OrderDetailViewModel.swift | 37 +++++++++++++++++++ .../OrderList/OrderListViewController.swift | 19 ++++++++-- .../OrderList/OrderListViewModel.swift | 20 ++++++---- 6 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewModel.swift diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index d07b841..efede58 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 006FA0962833775400281C4D /* OrderListTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0952833775400281C4D /* OrderListTableViewDelegate.swift */; }; 006FA0982833785B00281C4D /* OrderListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006FA0972833785B00281C4D /* OrderListViewModel.swift */; }; 00AC77CD2833FA3B00BBBF43 /* DetailListEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00AC77CC2833FA3B00BBBF43 /* DetailListEntity.swift */; }; + 1E71CB232835154C00F6D2D0 /* OrderDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E71CB222835154C00F6D2D0 /* OrderDetailViewModel.swift */; }; 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */; }; 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */; }; E0039B56282FD92800298719 /* WhatsNewListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0039B55282FD92800298719 /* WhatsNewListViewController.swift */; }; @@ -100,6 +101,7 @@ 006FA0972833785B00281C4D /* OrderListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderListViewModel.swift; sourceTree = ""; }; 00AC77CC2833FA3B00BBBF43 /* DetailListEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailListEntity.swift; sourceTree = ""; }; 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.debug.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.debug.xcconfig"; sourceTree = ""; }; + 1E71CB222835154C00F6D2D0 /* OrderDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailViewModel.swift; sourceTree = ""; }; 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StarbucksTests.release.xcconfig"; path = "Target Support Files/Pods-StarbucksTests/Pods-StarbucksTests.release.xcconfig"; sourceTree = ""; }; 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StarbucksTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Starbucks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -401,6 +403,7 @@ isa = PBXGroup; children = ( E0723389282B7DB900AF3E16 /* OrderDetailViewController.swift */, + 1E71CB222835154C00F6D2D0 /* OrderDetailViewModel.swift */, ); path = OrderDetail; sourceTree = ""; @@ -844,6 +847,7 @@ E07233F2282D235100AF3E16 /* NSMutableAttributedString+Extension.swift in Sources */, E0108CF02833403900CC736C /* CardListViewCell.swift in Sources */, E07233D4282BCBBC00AF3E16 /* Inject.swift in Sources */, + 1E71CB232835154C00F6D2D0 /* OrderDetailViewModel.swift in Sources */, E07233B0282B7DB900AF3E16 /* OrderListViewController.swift in Sources */, E0039B58282FD93200298719 /* WhatsNewListViewModel.swift in Sources */, E07233B2282B7DB900AF3E16 /* OrderTableViewDataSource.swift in Sources */, diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 168a47c..e89cb4c 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -105,10 +105,13 @@ class HomeViewController: UIViewController { viewModel.state().presentProductDetailView .withUnretained(self) - .bind(onNext: { vc, product in - let viewController = OrderDetailViewController() - vc.navigationController?.pushViewController(viewController, animated: true) - vc.tabBarController?.tabBar.isHidden = true + .bind(onNext: { currentVC, productCode in + let viewModel = OrderDetailViewModel(productCode: productCode) + let presentVC = OrderDetailViewController(viewModel: viewModel) + presentVC.title = productCode + currentVC.navigationController?.navigationBar.isHidden = false + currentVC.navigationController?.pushViewController(presentVC, animated: true) + //TODO: presentVC의 BackButton을 presentVC가 스스로 만들기 }) .disposed(by: disposeBag) diff --git a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift index 1387fa2..9f518b3 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift @@ -10,8 +10,10 @@ import UIKit class OrderDetailViewController: UIViewController { private let disposeBag = DisposeBag() - - init() { + private let viewModel: OrderDetailViewModelProtocol + + init(viewModel: OrderDetailViewModelProtocol) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) bind() attribute() diff --git a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewModel.swift new file mode 100644 index 0000000..af05335 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewModel.swift @@ -0,0 +1,37 @@ +// +// OrderDetailViewModel.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/18. +// + +import Foundation +import RxRelay +import RxSwift + +protocol OrderDetailViewModelAction { +} + +protocol OrderDetailViewModelState { +} + +protocol OrderDetailViewModelBinding { + func action() -> OrderDetailViewModelAction + func state() -> OrderDetailViewModelState +} + +typealias OrderDetailViewModelProtocol = OrderDetailViewModelBinding + +class OrderDetailViewModel: OrderDetailViewModelAction, OrderDetailViewModelState, OrderDetailViewModelBinding { + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + func action() -> OrderDetailViewModelAction { self } + + func state() -> OrderDetailViewModelState { self } + + init(productCode: String) { + + } +} diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index cdc44a2..7c8bad1 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -52,10 +52,6 @@ class OrderListViewController: UIViewController { viewModel.state().updatedTitle .bind(to: categoryLabel.rx.text) .disposed(by: disposeBag) - - tableViewHandler.selectedCellIndex - .bind(to: viewModel.action().loadDetail) - .disposed(by: disposeBag) viewModel.state().updatedList .bind(onNext: tableViewDatasource.update) @@ -64,6 +60,21 @@ class OrderListViewController: UIViewController { viewModel.state().reloadedList .bind(onNext: tableView.reloadData) .disposed(by: disposeBag) + + tableViewHandler.selectedCellIndex + .bind(to: viewModel.action().loadProductCode) + .disposed(by: disposeBag) + + viewModel.state().loadedProductCode + .withUnretained(self) + .bind(onNext: { currentVC, productCode in + let viewModel = OrderDetailViewModel(productCode: productCode) + let presentVC = OrderDetailViewController(viewModel: viewModel) + presentVC.title = productCode + + currentVC.navigationController?.pushViewController(presentVC, animated: true) + }) + .disposed(by: disposeBag) } private func attribute() { diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift index 29ed0f8..a735bcf 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -10,14 +10,13 @@ import RxRelay import RxSwift protocol OrderListViewModelAction { - var loadDetail: PublishRelay { get } + var loadProductCode: PublishRelay { get } var loadList: PublishRelay { get } var updateTitle: PublishRelay { get } } protocol OrderListViewModelState { - var loadedDetail: PublishRelay { get } - var loadedDetailImage: PublishRelay { get } + var loadedProductCode: PublishRelay { get } var updatedList: PublishRelay<[Product]> { get } var reloadedList: PublishRelay { get } var updatedTitle: PublishRelay { get } @@ -37,14 +36,13 @@ class OrderListViewModel: OrderListViewModelAction, OrderListViewModelState, Ord func action() -> OrderListViewModelAction { self } - let loadDetail = PublishRelay() + let loadProductCode = PublishRelay() let loadList = PublishRelay() let updateTitle = PublishRelay() func state() -> OrderListViewModelState { self } - var loadedDetail = PublishRelay() - var loadedDetailImage = PublishRelay() + var loadedProductCode = PublishRelay() let updatedList = PublishRelay<[Product]>() let reloadedList = PublishRelay() let updatedTitle = PublishRelay() @@ -62,7 +60,7 @@ class OrderListViewModel: OrderListViewModelAction, OrderListViewModelState, Ord model.starbucksRepository.requestCategoryProduct(subCategory).asObservable() } .share() - + requestProducts .compactMap { $0.value?.products } .withUnretained(self) @@ -72,5 +70,13 @@ class OrderListViewModel: OrderListViewModelAction, OrderListViewModelState, Ord .map { _ in } .bind(to: reloadedList) .disposed(by: disposeBag) + + action().loadProductCode + .withLatestFrom(requestProducts) { + $1.value?.products[$0] + } + .compactMap { $0?.productCode } + .bind(to: loadedProductCode) + .disposed(by: disposeBag) } } From 2b90bc17419fba036f6b38c3ea00990d488f1645 Mon Sep 17 00:00:00 2001 From: shingha Date: Thu, 19 May 2022 13:51:50 +0900 Subject: [PATCH 55/57] =?UTF-8?q?feature:=20navigation=20UINavigationBarAp?= =?UTF-8?q?pearance=20=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=B4=EC=84=9C=20?= =?UTF-8?q?=EC=93=B0=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Starbucks/Starbucks.xcodeproj/project.pbxproj | 8 -- .../Present/Home/HomeViewController.swift | 16 ++- .../Present/Pay/PayViewController.swift | 66 +++-------- .../WhatsNewListViewController.swift | 111 ++++-------------- 4 files changed, 53 insertions(+), 148 deletions(-) diff --git a/Starbucks/Starbucks.xcodeproj/project.pbxproj b/Starbucks/Starbucks.xcodeproj/project.pbxproj index d07b841..9ab0826 100644 --- a/Starbucks/Starbucks.xcodeproj/project.pbxproj +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -461,7 +461,6 @@ isa = PBXGroup; children = ( E0108CE02831310E00CC736C /* Camera */, - E07233F6282D515800AF3E16 /* View */, E07233D2282BCBA900AF3E16 /* PropertyWrapper */, E072339B282B7DB900AF3E16 /* Log.swift */, E07233D0282BCB5E00AF3E16 /* Container.swift */, @@ -563,13 +562,6 @@ path = HomeInfoView; sourceTree = ""; }; - E07233F6282D515800AF3E16 /* View */ = { - isa = PBXGroup; - children = ( - ); - path = View; - sourceTree = ""; - }; E08BB6AD28294347005ADEFA /* StarbucksTests */ = { isa = PBXGroup; children = ( diff --git a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift index 168a47c..1eef37c 100644 --- a/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -80,17 +80,23 @@ class HomeViewController: UIViewController { .bind(to: viewModel.action().loadHome) .disposed(by: disposeBag) - rx.viewDidAppear - .map { _ in } + rx.viewWillAppear .withUnretained(self) - .bind(onNext: { vc, _ in - vc.navigationController?.navigationBar.isHidden = true + .bind(onNext: { vc, animated in + vc.navigationController?.setNavigationBarHidden(true, animated: animated) // vc.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) // vc.navigationController?.navigationBar.shadowImage = UIImage() // vc.navigationController?.navigationBar.backgroundColor = .clear }) .disposed(by: disposeBag) + rx.viewWillDisappear + .withUnretained(self) + .bind(onNext: { vc, animated in + vc.navigationController?.setNavigationBarHidden(false, animated: animated) + }) + .disposed(by: disposeBag) + rx.viewDidAppear .withUnretained(self) .bind(onNext: { vc, _ in @@ -120,6 +126,7 @@ class HomeViewController: UIViewController { .withUnretained(self) .bind(onNext: { vc, _ in let viewController = WhatsNewListViewController(viewModel: WhatsNewListViewModel()) + vc.navigationItem.backButtonTitle = "" vc.navigationController?.pushViewController(viewController, animated: true) }) .disposed(by: disposeBag) @@ -127,6 +134,7 @@ class HomeViewController: UIViewController { private func attribute() { scrollView.delegate = self + view.backgroundColor = .white } private func layout() { diff --git a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift index 8a469fe..aea1186 100644 --- a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift @@ -25,18 +25,7 @@ class PayViewController: UIViewController { stackView.spacing = 20 return stackView }() - - private let titleView = UIView() - - private let titleLabel: UILabel = { - let label = UILabel() - label.text = "Pay" - label.textAlignment = .left - label.font = .systemFont(ofSize: 24, weight: .bold) - label.textColor = .black - return label - }() - + private lazy var cardListViewController: CardListViewController = { let viewController = CardListViewController(viewModel: viewModel.cardListViewModel) return viewController @@ -79,16 +68,23 @@ class PayViewController: UIViewController { } private func bind() { - rx.viewDidAppear - .map { _ in } + rx.viewWillAppear .withUnretained(self) - .bind(onNext: { vc, _ in - let titleColor: UIColor = .black.withAlphaComponent(0) - vc.title = "Pay" - vc.navigationController?.navigationBar.barTintColor = .white - vc.navigationController?.navigationBar.isTranslucent = false - vc.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: titleColor] - vc.navigationItem.rightBarButtonItem = vc.tappedAddCard + .bind(onNext: { vc, animated in + vc.navigationController?.navigationBar.prefersLargeTitles = true + + let appearance = UINavigationBarAppearance() + appearance.backgroundColor = .white + appearance.titleTextAttributes = [.foregroundColor: UIColor.black] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.black] + + vc.navigationController?.navigationBar.tintColor = .black + //기본상태( 스크롤 있는 경우 아래로 이동했을 때 ) + vc.navigationController?.navigationBar.standardAppearance = appearance + //가로화면으로 볼 때 + vc.navigationController?.navigationBar.compactAppearance = appearance + //스크롤의 최 상단일 때 + vc.navigationController?.navigationBar.scrollEdgeAppearance = appearance }) .disposed(by: disposeBag) @@ -125,7 +121,8 @@ class PayViewController: UIViewController { } private func attribute() { - scrollView.delegate = self + title = "Pay" + navigationItem.rightBarButtonItem = tappedAddCard } private func layout() { @@ -133,9 +130,7 @@ class PayViewController: UIViewController { view.addSubview(chargeView) scrollView.addSubview(contentStackView) - contentStackView.addArrangedSubview(titleView) contentStackView.addArrangedSubview(cardListViewController.view) - titleView.addSubview(titleLabel) chargeView.addSubview(previewView) chargeView.addSubview(captureButton) @@ -154,15 +149,6 @@ class PayViewController: UIViewController { $0.leading.trailing.equalToSuperview() } - titleView.snp.makeConstraints { - $0.height.equalTo(titleLabel) - } - - titleLabel.snp.makeConstraints { - $0.top.bottom.equalToSuperview() - $0.leading.equalToSuperview().offset(20) - } - chargeView.snp.makeConstraints { $0.edges.equalToSuperview() } @@ -178,17 +164,3 @@ class PayViewController: UIViewController { } } } - -extension PayViewController: UIScrollViewDelegate { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - var alpha: CGFloat = 0 - if scrollView.contentOffset.y < 0 { - alpha = 0 - } else { - alpha = scrollView.contentOffset.y / titleLabel.frame.size.height - alpha = alpha > 1 ? 1 : alpha - } - - navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.black.withAlphaComponent(alpha)] - } -} diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift index 2f04236..450c8ae 100644 --- a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift @@ -9,33 +9,6 @@ import RxSwift import UIKit class WhatsNewListViewController: UIViewController { - enum Constants { - static let navigationHeight = 50.0 - static let tableHeaderViewHeight = 80.0 - } - - private let navigationView: UIView = { - let view = UIView() - view.backgroundColor = .white - return view - }() - - private let titleLabel: UILabel = { - let label = UILabel() - label.text = "What's New" - label.font = .systemFont(ofSize: 18, weight: .bold) - label.alpha = 0 - return label - }() - - private let backButton: UIButton = { - let button = UIButton() - button.setImage(UIImage(systemName: "chevron.backward"), for: .normal) - button.imageView?.tintColor = .black - return button - }() - - private let tableHeaderView = UIView() private let tableView: UITableView = { let tableView = UITableView() @@ -44,13 +17,6 @@ class WhatsNewListViewController: UIViewController { return tableView }() - private let tableTitleLabel: UILabel = { - let label = UILabel() - label.text = "What's New" - label.font = .systemFont(ofSize: 23, weight: .bold) - return label - }() - private let viewModel: WhatsNewListViewProtocol private let disposeBag = DisposeBag() private let dataSource = WhatsNewListDataSource() @@ -77,6 +43,26 @@ class WhatsNewListViewController: UIViewController { .bind(to: viewModel.action().loadEvents) .disposed(by: disposeBag) + rx.viewWillAppear + .withUnretained(self) + .bind(onNext: { vc, _ in + vc.navigationController?.navigationBar.prefersLargeTitles = true + + let appearance = UINavigationBarAppearance() + appearance.backgroundColor = .white + appearance.titleTextAttributes = [.foregroundColor: UIColor.black] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.black] + + vc.navigationController?.navigationBar.tintColor = .black + //기본상태( 스크롤 있는 경우 아래로 이동했을 때 ) + vc.navigationController?.navigationBar.standardAppearance = appearance + //가로화면으로 볼 때 + vc.navigationController?.navigationBar.compactAppearance = appearance + //스크롤의 최 상단일 때 + vc.navigationController?.navigationBar.scrollEdgeAppearance = appearance + }) + .disposed(by: disposeBag) + viewModel.state().updateEvents .bind(onNext: dataSource.updateEvents) .disposed(by: disposeBag) @@ -84,74 +70,21 @@ class WhatsNewListViewController: UIViewController { viewModel.state().reloadEvents .bind(onNext: tableView.reloadData) .disposed(by: disposeBag) - - backButton.rx.tap - .withUnretained(self) - .bind(onNext: { vc, _ in - vc.navigationController?.popViewController(animated: true) - }) - .disposed(by: disposeBag) } private func attribute() { + title = "What's New" view.backgroundColor = .white tableView.dataSource = dataSource - tableView.delegate = self - tableView.tableHeaderView = tableHeaderView } private func layout() { - view.addSubview(navigationView) - navigationView.addSubview(titleLabel) - navigationView.addSubview(backButton) - view.addSubview(tableView) - tableHeaderView.addSubview(tableTitleLabel) - - navigationView.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide) - $0.leading.trailing.equalToSuperview() - $0.height.equalTo(Constants.navigationHeight) - } - - titleLabel.snp.makeConstraints { - $0.top.bottom.centerX.equalToSuperview() - } - - backButton.snp.makeConstraints { - $0.leading.equalToSuperview() - $0.centerY.equalToSuperview() - $0.width.height.equalTo(navigationView.snp.height) - } - - tableHeaderView.snp.makeConstraints { - $0.height.equalTo(Constants.tableHeaderViewHeight) - $0.width.equalToSuperview() - } - - tableTitleLabel.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.leading.equalToSuperview().offset(20) - } tableView.snp.makeConstraints { - $0.top.equalTo(navigationView.snp.bottom) + $0.top.equalTo(view.safeAreaLayoutGuide) $0.leading.trailing.equalToSuperview() $0.bottom.equalTo(view.safeAreaLayoutGuide) } } } - -extension WhatsNewListViewController: UITableViewDelegate { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - var alpha: CGFloat = 0 - if scrollView.contentOffset.y < 0 { - alpha = 0 - } else { - alpha = scrollView.contentOffset.y / tableHeaderView.frame.height - alpha = alpha > 1 ? 1 : alpha - } - - titleLabel.alpha = alpha - } -} From 2f037d3e274ff64799352c6df7e26c9467e762b1 Mon Sep 17 00:00:00 2001 From: Damagucci-Juice Date: Thu, 19 May 2022 18:03:09 +0900 Subject: [PATCH 56/57] =?UTF-8?q?[STAR-28]=20feat:=20OrderDeatailView=20UI?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrderDetailViewController.swift | 171 ++++++++++++++++++ .../OrderList/OrderListViewController.swift | 1 - .../Starbucks/Sources/Present/Pay/.DS_Store | Bin 0 -> 6148 bytes .../Sources/Present/Pay/View/.DS_Store | Bin 0 -> 6148 bytes 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/.DS_Store create mode 100644 Starbucks/Starbucks/Sources/Present/Pay/View/.DS_Store diff --git a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift index 9f518b3..0ce4d6c 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift @@ -6,12 +6,141 @@ // import RxSwift +import SnapKit import UIKit +enum Constant { + static let titleSize: CGFloat = 32 + static let productDetailInfoSize: CGFloat = 16 + static let kiloCaloriesSize: CGFloat = 17 +} + class OrderDetailViewController: UIViewController { private let disposeBag = DisposeBag() private let viewModel: OrderDetailViewModelProtocol + // MARK: 바탕부 + private let contentView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + return stackView + }() + + private let backgroundScrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.backgroundColor = .white + scrollView.showsVerticalScrollIndicator = false + return scrollView + }() + + // MARK: 상단부 + private let productImage: UIImageView = { + let imageView = UIImageView() + let image = UIImage(systemName: "pencil") + imageView.image = image + return imageView + }() + + // MARK: 중단 + private let heartButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "heart"), for: .normal) + return button + }() + + private let productName: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.titleSize, weight: .bold) + label.text = "나이트로 바닐라 크림" + return label + }() + + private let productEnglishName: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.productDetailInfoSize, weight: .light) + label.text = "Nitro Vanila Cream" + return label + }() + + private let content: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.productDetailInfoSize, weight: .regular) + label.text = """ + 진하고 깊은 풍미의 나이트로 + 바닐라 크림을 즐겨보세요 + """ + return label + }() + + private let price: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.titleSize, weight: .bold) + label.text = "5900원" + return label + }() + + private let descriptionStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + stackView.distribution = .equalSpacing + stackView.spacing = 10 + return stackView + }() + + // MARK: 상세 영양 정보표 + private let caloriesNameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.productDetailInfoSize, weight: .light) + label.text = "칼로리" + return label + }() + + private let caloriesDecimalLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.kiloCaloriesSize, weight: .light) + label.text = "150kcal" + return label + }() + + private let caloriesStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .equalSpacing + stackView.backgroundColor = .red + return stackView + }() + + private let carbohydrateNameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.productDetailInfoSize, weight: .light) + label.text = "탄수화물" + return label + }() + + private let carbohydrateDecimalLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: Constant.kiloCaloriesSize, weight: .light) + label.text = "20g" + return label + }() + + private let carbohydrateStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .equalSpacing + stackView.backgroundColor = .yellow + return stackView + }() + + private let nutritionStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fillProportionally + return stackView + }() + init(viewModel: OrderDetailViewModelProtocol) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) @@ -31,6 +160,7 @@ class OrderDetailViewController: UIViewController { .withUnretained(self) .bind(onNext: { vc, _ in vc.navigationController?.isNavigationBarHidden = false + vc.tabBarController?.tabBar.isHidden = true }) .disposed(by: disposeBag) } @@ -40,5 +170,46 @@ class OrderDetailViewController: UIViewController { } private func layout() { + + view.addSubview(backgroundScrollView) + backgroundScrollView.addSubview(contentView) + contentView.addArrangedSubview(productImage) + contentView.addArrangedSubview(descriptionStackView) + contentView.addArrangedSubview(nutritionStackView) + descriptionStackView.addArrangedSubview(productName) + descriptionStackView.addArrangedSubview(productEnglishName) + descriptionStackView.addArrangedSubview(content) + descriptionStackView.addArrangedSubview(price) + + nutritionStackView.addArrangedSubview(caloriesStackView) + nutritionStackView.addArrangedSubview(carbohydrateStackView) + + caloriesStackView.addArrangedSubview(caloriesNameLabel) + caloriesStackView.addArrangedSubview(caloriesDecimalLabel) + carbohydrateStackView.addArrangedSubview(carbohydrateNameLabel) + carbohydrateStackView.addArrangedSubview(carbohydrateDecimalLabel) + + backgroundScrollView.snp.makeConstraints { + $0.top.leading.trailing.bottom.equalToSuperview() + } + + contentView.snp.makeConstraints { + $0.top.bottom.equalTo(backgroundScrollView) + $0.leading.trailing.equalTo(backgroundScrollView).inset(20) + $0.width.equalTo(backgroundScrollView) + } + + productImage.snp.makeConstraints { + $0.top.leading.trailing.equalTo(contentView) + $0.height.equalTo(300) + } + + descriptionStackView.snp.makeConstraints { + $0.leading.trailing.equalTo(contentView) + } + + nutritionStackView.snp.makeConstraints { + $0.leading.trailing.equalTo(contentView) + } } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift index 7c8bad1..5fbc7ac 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -71,7 +71,6 @@ class OrderListViewController: UIViewController { let viewModel = OrderDetailViewModel(productCode: productCode) let presentVC = OrderDetailViewController(viewModel: viewModel) presentVC.title = productCode - currentVC.navigationController?.pushViewController(presentVC, animated: true) }) .disposed(by: disposeBag) diff --git a/Starbucks/Starbucks/Sources/Present/Pay/.DS_Store b/Starbucks/Starbucks/Sources/Present/Pay/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f7affc9b06536da648877a64ae56280a85bfac11 GIT binary patch literal 6148 zcmeH~u?oUK42Bc!P;lw!c#99<8yrQSKyY?YL=Xi*UGLHTlM8~?Sw#Lo@?UZnO247k zh=}&r?Odc2krr+$GYbP#4)x;*FFyfD>?)qZ-LRi*0nMSMDk?Dk2sj1?68I{C E2cYs2t^fc4 literal 0 HcmV?d00001 diff --git a/Starbucks/Starbucks/Sources/Present/Pay/View/.DS_Store b/Starbucks/Starbucks/Sources/Present/Pay/View/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..20888d8b5030943d052fa3686d55a2c06dd3243d GIT binary patch literal 6148 zcmeHK%}T>S5T317Hx!`<1&;x*qV*TVOHAuQ@MJ^}Dm5`hgE1>jY7eE5v%Zi|;_K+l z?pBoQQL!=uv)^QXlI*u(Hv<5|@5O5XbpVj4gcS#eFNETxV^Xo6DMX>~aR>?IP#mP{ zYAl-_e~|%NI|)q)p$jp5SidM$5yt4l*h5`;)q>U+`B6W=xB1V}eVXJ&qwyjtQ`PC2 zSvf0f@*=oV{h$boUhaq8GrBrfDvd^V7@Z`Ye!D*3R9O)wS*NQDlDLB@=ch>)tG=&# zS)A+Izyzctopyb3FxYA|mfg+m&Ec{;*jXogV|zGs>5SYmANRM){1)05iZ0Ofmy@Pm{Gto&>);Gr$b|2m`b~xTu7l#nPbO zI&jb}0Ady0TF|CnMyhcwdKOEA7(o%H6w#Cl+hPb)j(*qXc@|59rW}NAK7@U;upNrf zPsjVaIvs>(kVj^K8TiZq^?oRII{){-@BeiY56l2F@IM(4)mG5*ac=f(9h)VcwJz!< rDhcJ42H#82&_^-m(owvKss;Tn8Hk?6(jasW7(rP7 literal 0 HcmV?d00001 From bcd898d0137ebe87f81cc9ee1a463a35f76cb37d Mon Sep 17 00:00:00 2001 From: shingha Date: Thu, 19 May 2022 23:47:46 +0900 Subject: [PATCH 57/57] =?UTF-8?q?featrue:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrderCategory/CategoryTableViewCell.swift | 5 + .../OrderCategoryViewController.swift | 134 +++++++++--------- .../OrderTableViewDataSource.swift | 9 +- .../OrderTableViewDelegate.swift | 27 ++-- .../OrderCategory/OrderViewModel.swift | 70 +++++---- 5 files changed, 133 insertions(+), 112 deletions(-) diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift index 8b1f396..ef3c120 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift @@ -63,7 +63,12 @@ class CategoryTableViewCell: UITableViewCell { menuNameStackView.addArrangedSubview(mainNameLabel) menuNameStackView.addArrangedSubview(subNameLabel) + contentView.snp.makeConstraints { + $0.bottom.equalTo(menuImageView).offset(10) + } + menuImageView.snp.makeConstraints { + $0.top.equalToSuperview().offset(10) $0.leading.equalToSuperview().offset(20) $0.centerY.equalToSuperview() $0.width.height.equalTo(80) diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift index 18c216c..3a2df25 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -13,31 +13,22 @@ import UIKit class OrderCategoryViewController: UIViewController { - private let tableView = UITableView() - private var tableViewDataSource: OrderTableViewDataSource? - private let tableViewHandler = OrderTableViewDelegate() + let test = UIView() - private let orderLabel: UILabel = { - let label = UILabel() - label.text = "Order" - label.font = .systemFont(ofSize: 28, weight: .bold) - label.textAlignment = .left - return label - }() - - private let backBarButtonItem: UIBarButtonItem = { - let barButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) - barButtonItem.tintColor = .systemGray - return barButtonItem - }() - - private let categoryView: UIView = { + private let tableSectionHeaderView: UIView = { let view = UIView() - view.layer.borderWidth = 1 - view.layer.borderColor = UIColor.systemGray.cgColor + view.backgroundColor = .white return view }() + private let tableView: UITableView = { + let tableView = UITableView() + tableView.register(CategoryTableViewCell.self, forCellReuseIdentifier: CategoryTableViewCell.identifier) + tableView.separatorStyle = .none + tableView.contentInset.top = 0 + return tableView + }() + private let categoryStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal @@ -56,6 +47,13 @@ class OrderCategoryViewController: UIViewController { } }() + private let categoryViewBar: UIView = { + let view = UIView() + view.backgroundColor = .lightGray + return view + }() + + private var tableViewDataSource = OrderTableViewDataSource() private let viewModel: OrderViewModelProtocol private let disposeBag = DisposeBag() @@ -77,18 +75,36 @@ class OrderCategoryViewController: UIViewController { .bind(to: viewModel.action().loadCategory) .disposed(by: disposeBag) - viewModel.state().loadedCategory - .bind(onNext: { menu in - self.updateDatasource(menu: menu) + rx.viewWillAppear + .withUnretained(self) + .bind(onNext: { vc, _ in + vc.navigationController?.navigationBar.prefersLargeTitles = true + + let appearance = UINavigationBarAppearance() + appearance.backgroundColor = .white + appearance.titleTextAttributes = [.foregroundColor: UIColor.black] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.black] + appearance.shadowColor = .clear + + vc.navigationController?.navigationBar.tintColor = .black + //기본상태( 스크롤 있는 경우 아래로 이동했을 때 ) + vc.navigationController?.navigationBar.standardAppearance = appearance + //가로화면으로 볼 때 + vc.navigationController?.navigationBar.compactAppearance = appearance + //스크롤의 최 상단일 때 + vc.navigationController?.navigationBar.scrollEdgeAppearance = appearance }) .disposed(by: disposeBag) - tableViewHandler.selectedCellIndex - .bind(to: viewModel.action().tapMenu) + viewModel.state().updateList + .bind(onNext: tableViewDataSource.update) + .disposed(by: disposeBag) + + viewModel.state().reloadList + .bind(onNext: tableView.reloadData) .disposed(by: disposeBag) viewModel.state().selectedCategory - .map { $0.index } .bind(onNext: { selectIndex in self.categoryButtons.enumerated().forEach { index, button in button.isSelected = index == selectIndex @@ -98,9 +114,7 @@ class OrderCategoryViewController: UIViewController { categoryButtons.enumerated().forEach { index, button in button.rx.tap - .compactMap { _ in - Category.GroupType.indexToCase(index) - } + .map { _ in index } .bind(to: viewModel.action().tappedCategory) .disposed(by: disposeBag) } @@ -110,66 +124,58 @@ class OrderCategoryViewController: UIViewController { .bind(onNext: { model, subCategory in let viewModel = OrderListViewModel(subCategory: subCategory.groupId, title: subCategory.title) let orderListVC = OrderListViewController(viewModel: viewModel) - model.navigationItem.backBarButtonItem = model.backBarButtonItem + model.navigationItem.backButtonTitle = "" model.navigationController?.pushViewController(orderListVC, animated: true) }) .disposed(by: disposeBag) } private func attribute() { - view.backgroundColor = .systemBackground - configureTableView() + title = "Order" + view.backgroundColor = .white + tableView.delegate = self + tableView.dataSource = tableViewDataSource } private func layout() { - view.addSubview(orderLabel) - view.addSubview(categoryView) - categoryView.addSubview(categoryStackView) - view.addSubview(tableView) + tableSectionHeaderView.addSubview(categoryStackView) + tableSectionHeaderView.addSubview(categoryViewBar) categoryButtons.forEach { categoryStackView.addArrangedSubview($0) } - orderLabel.snp.makeConstraints { - $0.top.equalToSuperview().offset(80) - $0.leading.trailing.equalToSuperview().inset(20) - } - - categoryView.snp.makeConstraints { - $0.top.equalTo(orderLabel.snp.bottom).offset(40) - $0.leading.trailing.equalToSuperview().inset(-1) - $0.bottom.equalTo(categoryStackView) + categoryStackView.snp.makeConstraints { + $0.top.bottom.equalToSuperview() + $0.trailing.equalTo(categoryButtons[categoryButtons.count - 1]) + $0.leading.equalToSuperview().offset(20) } - categoryStackView.snp.makeConstraints { - $0.top.equalToSuperview().offset(2) - $0.leading.equalToSuperview().offset(30) - $0.height.equalTo(45) + categoryViewBar.snp.makeConstraints { + $0.leading.trailing.bottom.equalToSuperview() + $0.height.equalTo(1) } tableView.snp.makeConstraints { - $0.top.equalTo(categoryView.snp.bottom) + $0.top.equalToSuperview() $0.leading.trailing.equalToSuperview() $0.bottom.equalTo(view.safeAreaLayoutGuide) } } - - private func configureTableView() { - tableView.rowHeight = 100 - tableView.separatorStyle = .none - tableView.register(CategoryTableViewCell.self, forCellReuseIdentifier: CategoryTableViewCell.identifier) - tableView.delegate = tableViewHandler - } } -extension OrderCategoryViewController { - private func updateDatasource(menu: [Category.Group]) { - self.tableViewDataSource = OrderTableViewDataSource(menus: menu) - DispatchQueue.main.async { [weak self] in - self?.tableView.dataSource = self?.tableViewDataSource - self?.tableView.reloadData() - } +extension OrderCategoryViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + tableSectionHeaderView + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + 50 + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + viewModel.action().tappedMenu.accept(indexPath.item) } } diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift index 6ddfe8d..31aa546 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift @@ -11,11 +11,14 @@ import UIKit class OrderTableViewDataSource: NSObject, UITableViewDataSource { - private let menus: [Category.Group] + private var menus: [Category.Group] = [] - init(menus: [Category.Group]) { + func update(menus: [Category.Group]) { self.menus = menus - super.init() + } + + func numberOfSections(in tableView: UITableView) -> Int { + 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift index 7cea1be..6c595ed 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift @@ -9,17 +9,16 @@ import RxRelay import RxSwift import UIKit -protocol CellSelectionDetectable: AnyObject { - func didSelectCell(indexPath: IndexPath) -} - -class OrderTableViewDelegate: NSObject, UITableViewDelegate { - - let selectedCellIndex = PublishSubject() - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - selectedCellIndex - .onNext(indexPath.row) - } -} +//protocol CellSelectionDetectable: AnyObject { +// func didSelectCell(indexPath: IndexPath) +//} +// +//class OrderTableViewDelegate: NSObject, UITableViewDelegate { +// +// let selectedCellIndex = PublishSubject() +// +// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { +// +// selectedCellIndexonNext(indexPath.row) +// } +//} diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift index acc801e..542bc4f 100644 --- a/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -11,13 +11,14 @@ import RxSwift protocol OrderViewModelAction { var loadCategory: PublishRelay { get } - var tappedCategory: PublishRelay { get } - var tapMenu: PublishRelay { get } + var tappedCategory: BehaviorRelay { get } + var tappedMenu: PublishRelay { get } } protocol OrderViewModelState { - var loadedCategory: PublishRelay<[Category.Group]> { get } - var selectedCategory: PublishRelay { get } + var updateList: PublishRelay<[Category.Group]> { get } + var reloadList: PublishRelay { get } + var selectedCategory: PublishRelay { get } var selectedSubCategory: PublishRelay { get } } @@ -30,28 +31,30 @@ typealias OrderViewModelProtocol = OrderViewModelBinding class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelBinding { + enum Contants { + static let firstCategory = Category.GroupType.beverage + } + func action() -> OrderViewModelAction { self } let loadCategory = PublishRelay() - let tappedCategory = PublishRelay() - let tapMenu = PublishRelay() + let tappedCategory = BehaviorRelay(value: Contants.firstCategory.index) + let tappedMenu = PublishRelay() func state() -> OrderViewModelState { self } - let loadedCategory = PublishRelay<[Category.Group]>() - let selectedCategory = PublishRelay() + let updateList = PublishRelay<[Category.Group]>() + let reloadList = PublishRelay() + let selectedCategory = PublishRelay() let selectedSubCategory = PublishRelay() @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository private let disposeBag = DisposeBag() - private var categoryMenu = Category.GroupType.allCases.reduce(into: [Category.GroupType: [Category.Group]]()) { - $0[$1] = [] - } + private var categoryMenu: [[Category.Group]] = [[]] init() { - // MARK: Repository에서 카테고리 Json 파일을 로드 let requestCategory = action().loadCategory .withUnretained(self) .flatMapLatest { model, _ in @@ -59,21 +62,24 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB } .share() - //로드가 성공하면 여기 로직 requestCategory - .compactMap { result in result.value } //값이 있는지 확인 + .compactMap { result in result.value } .map { groups in - groups.reduce(into: self.categoryMenu) { category, group in - category[group.category]?.append(group) + groups.reduce(into: [[Category.Group]].init(repeating: [], count: 3)) { category, group in + category[group.category.index].append(group) } - } //값이 있으면 Array -> dic로 변환 - .withUnretained(self) //weak self를 생략하기 위한 오퍼레이터 - .do { model, groups in model.categoryMenu = groups } //모델의 dic에 값을 넣어주고 - .map { _ in .beverage } //처음 보여질 테이블은 beverage이므로 값을 넘겨줌 - .bind(to: tappedCategory) //tappedCategory 퍼블리시 실행 + } + .withUnretained(self) + .do { model, groups in + let index = model.tappedCategory.value + model.categoryMenu = groups + model.updateList.accept(groups[index]) + model.selectedCategory.accept(index) + } + .map { _ in } + .bind(to: reloadList) .disposed(by: disposeBag) - //로드가 실패하면 여기 로직 Observable .merge( requestCategory.compactMap { $0.error } @@ -83,18 +89,20 @@ class OrderViewModel: OrderViewModelAction, OrderViewModelState, OrderViewModelB }) .disposed(by: disposeBag) - action().tappedCategory //카테고리 응답 오면 시작 + tappedCategory + .withUnretained(self) + .map { model, index in model.categoryMenu[index] } .withUnretained(self) - .do { model, type in model.selectedCategory.accept(type) } //선택한 카테고리 이벤트 알림 - .compactMap { model, type in model.categoryMenu[type] } //선택한 카테고리 데이터 반환 - .bind(to: loadedCategory) //선택한 카테고리 데이터 전달 + .do { model, groups in model.updateList.accept(groups) } + .map { _ in } + .bind(to: reloadList) .disposed(by: disposeBag) - - action().tapMenu - .withLatestFrom(tappedCategory) { [weak self] in - self?.categoryMenu[$1]?[$0] + + tappedMenu + .withUnretained(self) + .map { model, index in + model.categoryMenu[model.tappedCategory.value][index] } - .compactMap { $0 } .bind(to: selectedSubCategory) .disposed(by: disposeBag) }