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/README.md b/README.md index 6a8d370..040fc8b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,72 @@ -# swift-starbucks -iOS 클래스 프로젝트 - 스타벅스 앱 +## 2022 StackBucks App + +------ + +### ProjectSetting + +- Language: Swift +- UI: Code Base + SnapKit +- Architecture: MVVM + RxSwift + +### Use Pods + +* SwiftLint, SnapKit, Alamofire, RxSwift, RxCocoa, RxAppState, Quick, Nimble + +### Developer + +* [Shingha](https://github.com/shingha1124), [Gucci](https://github.com/Damagucci-Juice), [Mase](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) + +------ + +# 앱 화면 + +------ + +## 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) + +* ### 스토리 + + * 등록된 카드를 좌우로 넘기며 확인 할 수 있다 + + * 충전된 금액을 확인 할 수 있다 + + * 머신러닝을 이용하여 현금을 촬영하여 금액을 충전 할 수 있다 + + 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..d7f2087 --- /dev/null +++ b/Starbucks/Podfile @@ -0,0 +1,18 @@ +use_frameworks! + +target 'Starbucks' do + pod 'SwiftLint' + + pod 'SnapKit', '~> 5.6.0' + pod 'Alamofire' + + 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 new file mode 100644 index 0000000..854d9c6 --- /dev/null +++ b/Starbucks/Starbucks.xcodeproj/project.pbxproj @@ -0,0 +1,1129 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; + 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 */ + +/* Begin PBXContainerItemProxy section */ + E08BB6B028294347005ADEFA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E01B526E28289D17009918AE /* Project object */; + proxyType = 1; + remoteGlobalIDString = E01B527528289D17009918AE; + remoteInfo = Starbucks; + }; +/* End PBXContainerItemProxy section */ + +/* 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 = ""; }; + 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; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarbuckTargetTest.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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E01B527328289D17009918AE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 71E1C78E63597B5AEE24B83E /* Pods_Starbucks.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E08BB6A928294347005ADEFA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9CFF3F031A8A9E5CAA2DDC05 /* Pods_StarbucksTests.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 */, + 0179ED7916AEC19F665042EB /* Pods-StarbucksTests.debug.xcconfig */, + 3A9DAC6A7B755F657653AF4D /* Pods-StarbucksTests.release.xcconfig */, + ); + 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 = ""; + }; + E0108CDD28312F0000CC736C /* Camera */ = { + isa = PBXGroup; + children = ( + E0108CDB28312EED00CC736C /* CameraRepository.swift */, + E0108CDE28312F0E00CC736C /* CameraRepositoryImpl.swift */, + ); + path = Camera; + sourceTree = ""; + }; + E0108CE02831310E00CC736C /* Camera */ = { + isa = PBXGroup; + children = ( + E0108CFC2834A32900CC736C /* View */, + E0108CE12831314800CC736C /* CameraSession.swift */, + E0108CE32831E8D600CC736C /* CameraSessionError.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 = ""; + }; + E0108CFC2834A32900CC736C /* View */ = { + isa = PBXGroup; + children = ( + E0108CE7283260C200CC736C /* PreviewView.swift */, + ); + path = View; + sourceTree = ""; + }; + E0108CFD2834A33C00CC736C /* Common */ = { + isa = PBXGroup; + children = ( + E07233F7282D516200AF3E16 /* GradientView.swift */, + E0108CFE2834A35200CC736C /* IntrinsicTableView.swift */, + ); + path = Common; + sourceTree = ""; + }; + E01B526D28289D17009918AE = { + isa = PBXGroup; + children = ( + E01B527828289D17009918AE /* Starbucks */, + E08BB6AD28294347005ADEFA /* StarbucksTests */, + E01B527728289D17009918AE /* Products */, + D660B86900EFFD9394B4E3A6 /* Pods */, + E7EA5F1EF67A45D40CCE639E /* Frameworks */, + ); + sourceTree = ""; + }; + E01B527728289D17009918AE /* Products */ = { + isa = PBXGroup; + children = ( + E01B527628289D17009918AE /* Starbucks.app */, + E08BB6AC28294347005ADEFA /* StarbucksTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + E01B527828289D17009918AE /* Starbucks */ = { + isa = PBXGroup; + children = ( + E072337E282B7DB900AF3E16 /* Sources */, + E01B528F2828A0C3009918AE /* Resource */, + E01B52902828A0CE009918AE /* Configuration */, + ); + path = Starbucks; + sourceTree = ""; + }; + E01B528F2828A0C3009918AE /* Resource */ = { + isa = PBXGroup; + children = ( + E0108CE52832558A00CC736C /* KoreaCash.mlmodel */, + E01B528228289D18009918AE /* Assets.xcassets */, + E07233C9282B82BF00AF3E16 /* Category.json */, + ); + path = Resource; + sourceTree = ""; + }; + E01B52902828A0CE009918AE /* Configuration */ = { + isa = PBXGroup; + children = ( + E01B528728289D18009918AE /* Info.plist */, + ); + path = Configuration; + sourceTree = ""; + }; + E0723056282A13AB00AF3E16 /* API */ = { + isa = PBXGroup; + children = ( + E0723057282A13C900AF3E16 /* StarbuckTargetTest.swift */, + ); + path = API; + sourceTree = ""; + }; + E072337E282B7DB900AF3E16 /* Sources */ = { + isa = PBXGroup; + children = ( + E072339D282B7DB900AF3E16 /* Model */, + E072339F282B7DB900AF3E16 /* API */, + E0723392282B7DB900AF3E16 /* Repository */, + E072337F282B7DB900AF3E16 /* Present */, + E0723397282B7DB900AF3E16 /* Extension */, + E072339A282B7DB900AF3E16 /* Common */, + E07233A8282B7DB900AF3E16 /* AppDelegate.swift */, + E07233A9282B7DB900AF3E16 /* SceneDelegate.swift */, + ); + path = Sources; + sourceTree = ""; + }; + E072337F282B7DB900AF3E16 /* Present */ = { + isa = PBXGroup; + children = ( + E0723386282B7DB900AF3E16 /* TabBar */, + E0723380282B7DB900AF3E16 /* Home */, + E0039B54282FD8C000298719 /* WhatsNewList */, + E0723384282B7DB900AF3E16 /* Pay */, + E0723388282B7DB900AF3E16 /* OrderDetail */, + E072338A282B7DB900AF3E16 /* OrderList */, + E072338C282B7DB900AF3E16 /* OrderCategory */, + E0108CFD2834A33C00CC736C /* Common */, + E0723383282B7DB900AF3E16 /* RootWindow.swift */, + ); + path = Present; + sourceTree = ""; + }; + E0723380282B7DB900AF3E16 /* Home */ = { + isa = PBXGroup; + children = ( + E07233D7282BD5B800AF3E16 /* View */, + E0723381282B7DB900AF3E16 /* HomeViewModel.swift */, + E0723382282B7DB900AF3E16 /* HomeViewController.swift */, + ); + path = Home; + sourceTree = ""; + }; + E0723384282B7DB900AF3E16 /* Pay */ = { + isa = PBXGroup; + children = ( + E0108CE928333C3900CC736C /* View */, + E0723385282B7DB900AF3E16 /* PayViewController.swift */, + E0108CD9283128CD00CC736C /* PayViewModel.swift */, + ); + path = Pay; + sourceTree = ""; + }; + E0723386282B7DB900AF3E16 /* TabBar */ = { + isa = PBXGroup; + children = ( + E0723387282B7DB900AF3E16 /* StarbucksViewController.swift */, + ); + path = TabBar; + sourceTree = ""; + }; + E0723388282B7DB900AF3E16 /* OrderDetail */ = { + isa = PBXGroup; + children = ( + E0723389282B7DB900AF3E16 /* OrderDetailViewController.swift */, + 1E71CB222835154C00F6D2D0 /* OrderDetailViewModel.swift */, + ); + path = OrderDetail; + sourceTree = ""; + }; + E072338A282B7DB900AF3E16 /* OrderList */ = { + isa = PBXGroup; + children = ( + E072338B282B7DB900AF3E16 /* OrderListViewController.swift */, + 006FA0972833785B00281C4D /* OrderListViewModel.swift */, + 006FA0952833775400281C4D /* OrderListTableViewDelegate.swift */, + 006FA0932833408200281C4D /* OrderListTableViewDataSource.swift */, + ); + path = OrderList; + sourceTree = ""; + }; + E072338C282B7DB900AF3E16 /* OrderCategory */ = { + isa = PBXGroup; + children = ( + E072338F282B7DB900AF3E16 /* OrderCategoryViewController.swift */, + E0723391282B7DB900AF3E16 /* OrderViewModel.swift */, + E0723390282B7DB900AF3E16 /* CategoryTableViewCell.swift */, + E072338D282B7DB900AF3E16 /* OrderTableViewDelegate.swift */, + E072338E282B7DB900AF3E16 /* OrderTableViewDataSource.swift */, + ); + path = OrderCategory; + sourceTree = ""; + }; + E0723392282B7DB900AF3E16 /* Repository */ = { + isa = PBXGroup; + children = ( + E0108CDD28312F0000CC736C /* Camera */, + E0723393282B7DB900AF3E16 /* Starbucks */, + E0723396282B7DB900AF3E16 /* NetworkRepository.swift */, + ); + path = Repository; + sourceTree = ""; + }; + E0723393282B7DB900AF3E16 /* Starbucks */ = { + isa = PBXGroup; + children = ( + E0723394282B7DB900AF3E16 /* StarbucksRepository.swift */, + E0723395282B7DB900AF3E16 /* StarbucksRepositoryImpl.swift */, + ); + path = Starbucks; + sourceTree = ""; + }; + E0723397282B7DB900AF3E16 /* Extension */ = { + isa = PBXGroup; + children = ( + E0723398282B7DB900AF3E16 /* UIColor+Extension.swift */, + E0723399282B7DB900AF3E16 /* Result+Extension.swift */, + E07233F1282D235100AF3E16 /* NSMutableAttributedString+Extension.swift */, + ); + path = Extension; + sourceTree = ""; + }; + E072339A282B7DB900AF3E16 /* Common */ = { + isa = PBXGroup; + children = ( + E0108CE02831310E00CC736C /* Camera */, + E07233D2282BCBA900AF3E16 /* PropertyWrapper */, + E072339B282B7DB900AF3E16 /* Log.swift */, + E07233D0282BCB5E00AF3E16 /* Container.swift */, + E07233D5282BCC8100AF3E16 /* ImageManager.swift */, + E0108CF5283365CE00CC736C /* UserStore.swift */, + ); + path = Common; + sourceTree = ""; + }; + E072339D282B7DB900AF3E16 /* Model */ = { + isa = PBXGroup; + children = ( + E072339E282B7DB900AF3E16 /* StarbucksEntity.swift */, + E07233C7282B7F5200AF3E16 /* CategoryEntity.swift */, + 006FA0912832433A00281C4D /* CategoryProductEntity.swift */, + 00AC77CC2833FA3B00BBBF43 /* DetailListEntity.swift */, + ); + path = Model; + sourceTree = ""; + }; + E072339F282B7DB900AF3E16 /* API */ = { + isa = PBXGroup; + children = ( + E07233A0282B7DB900AF3E16 /* StarbucksTarget.swift */, + E07233A1282B7DB900AF3E16 /* Base */, + ); + path = API; + sourceTree = ""; + }; + E07233A1282B7DB900AF3E16 /* Base */ = { + isa = PBXGroup; + children = ( + E07233A2282B7DB900AF3E16 /* APIError.swift */, + E07233A3282B7DB900AF3E16 /* HTTPContentType.swift */, + E07233A4282B7DB900AF3E16 /* Provider.swift */, + E07233A5282B7DB900AF3E16 /* BaseTarget.swift */, + E07233A6282B7DB900AF3E16 /* HTTPMethod.swift */, + E07233A7282B7DB900AF3E16 /* Response.swift */, + ); + path = Base; + sourceTree = ""; + }; + E07233D2282BCBA900AF3E16 /* PropertyWrapper */ = { + isa = PBXGroup; + children = ( + E07233D3282BCBBC00AF3E16 /* Inject.swift */, + E0108CF32833658200CC736C /* UserDefault.swift */, + ); + path = PropertyWrapper; + sourceTree = ""; + }; + E07233D7282BD5B800AF3E16 /* View */ = { + isa = PBXGroup; + children = ( + E07233F3282D3BD300AF3E16 /* HomeInfoView */, + E07233E4282D037500AF3E16 /* WhatsNew */, + E07233E1282CA70E00AF3E16 /* MainEvent */, + E07233DC282BDD9C00AF3E16 /* Recommand */, + ); + path = View; + sourceTree = ""; + }; + E07233DC282BDD9C00AF3E16 /* Recommand */ = { + isa = PBXGroup; + children = ( + E07233DA282BD71900AF3E16 /* RecommandMenuCellView.swift */, + E07233DD282BDDB200AF3E16 /* RecommandMenuDataSource.swift */, + E07233EB282D0BE500AF3E16 /* RecommandMenuViewModel.swift */, + E07233D8282BD60200AF3E16 /* RecommandMenuViewController.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 = ""; + }; + E07233F3282D3BD300AF3E16 /* HomeInfoView */ = { + isa = PBXGroup; + children = ( + E07233F4282D3BF000AF3E16 /* HomeInfoView.swift */, + ); + path = HomeInfoView; + sourceTree = ""; + }; + E08BB6AD28294347005ADEFA /* StarbucksTests */ = { + isa = PBXGroup; + children = ( + E0723056282A13AB00AF3E16 /* API */, + E08BB6AE28294347005ADEFA /* StarbucksTests.swift */, + ); + path = StarbucksTests; + sourceTree = ""; + }; + E7EA5F1EF67A45D40CCE639E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 663577AC173C2F6DE7BEDD8F /* Pods_Starbucks.framework */, + 5FB5B0DED07CE08CC9C4C6F9 /* Pods_StarbucksTests.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"; + }; + 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 */ + E01B526E28289D17009918AE /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1330; + LastUpgradeCheck = 1330; + TargetAttributes = { + E01B527528289D17009918AE = { + CreatedOnToolsVersion = 13.3.1; + }; + E08BB6AB28294347005ADEFA = { + CreatedOnToolsVersion = 13.3.1; + TestTargetID = E01B527528289D17009918AE; + }; + }; + }; + 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 */, + E08BB6AB28294347005ADEFA /* StarbucksTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E01B527428289D17009918AE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E01B528328289D18009918AE /* Assets.xcassets in Resources */, + E07233CA282B82BF00AF3E16 /* Category.json in Resources */, + ); + 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; + 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 = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\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 = ( + E0108CFF2834A35200CC736C /* IntrinsicTableView.swift in Sources */, + 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 */, + 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 */, + E07233BB282B7DB900AF3E16 /* Log.swift in Sources */, + 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 */, + 006FA0922832433A00281C4D /* CategoryProductEntity.swift in Sources */, + E07233F8282D516200AF3E16 /* GradientView.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 */, + E07233F0282D0F9D00AF3E16 /* WhatsNewCellView.swift in Sources */, + 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 */, + 006FA0942833408200281C4D /* OrderListTableViewDataSource.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 */, + 1E71CB232835154C00F6D2D0 /* OrderDetailViewModel.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 */, + 00AC77CD2833FA3B00BBBF43 /* DetailListEntity.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 */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E08BB6A828294347005ADEFA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E08BB6AF28294347005ADEFA /* StarbucksTests.swift in Sources */, + E0723058282A13C900AF3E16 /* StarbuckTargetTest.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; + 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 = 14.0; + 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 = 14.0; + 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_NSCameraUsageDescription = "카메라 사용합니다!"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + 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; + }; + 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_NSCameraUsageDescription = "카메라 사용합니다!"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + 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; + }; + name = Release; + }; + E08BB6B328294347005ADEFA /* Debug */ = { + 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; + 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 = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + 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 */ + 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; + }; + 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.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.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.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/.DS_Store b/Starbucks/Starbucks/.DS_Store new file mode 100644 index 0000000..7f90481 Binary files /dev/null and b/Starbucks/Starbucks/.DS_Store differ diff --git a/Starbucks/Starbucks/Configuration/Info.plist b/Starbucks/Starbucks/Configuration/Info.plist new file mode 100644 index 0000000..0eb786d --- /dev/null +++ b/Starbucks/Starbucks/Configuration/Info.plist @@ -0,0 +1,23 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + 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/.DS_Store b/Starbucks/Starbucks/Resource/.DS_Store new file mode 100644 index 0000000..6c6fa7d Binary files /dev/null and b/Starbucks/Starbucks/Resource/.DS_Store differ 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..9221b9b --- /dev/null +++ b/Starbucks/Starbucks/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/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/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 0000000..e85a76e Binary files /dev/null and b/Starbucks/Starbucks/Resource/Assets.xcassets/homeTopBg.imageset/homeTopBg.jpeg differ 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 0000000..ce11ce5 Binary files /dev/null and b/Starbucks/Starbucks/Resource/Assets.xcassets/ic_temp.imageset/ic_temp.png differ diff --git a/Starbucks/Starbucks/Resource/Category.json b/Starbucks/Starbucks/Resource/Category.json new file mode 100644 index 0000000..19ee2db --- /dev/null +++ b/Starbucks/Starbucks/Resource/Category.json @@ -0,0 +1,67 @@ +{ + "groups": [ + { + "groupId": "W0000171", + "title": "콜드브루", + "subTitle": "Cold Brew", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2021/04/[9200000000038]_20210430113202458.jpg", + "category": "BEVERAGE" + }, + { + "groupId": "W0000003", + "title": "에스프레소", + "subTitle": "Espresso", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2021/04/[20]_20210415144112678.jpg", + "category": "BEVERAGE" + }, + { + "groupId": "W0000004", + "title": "프라프치노", + "subTitle": "Frappuccino", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2021/04/[9200000002760]_20210415133558068.jpg", + "category": "BEVERAGE" + }, + { + "groupId": "W0000013", + "title": "브레드", + "subTitle": "Cold Brew", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000004027]_20220407090004058.jpg", + "category": "FOOD" + }, + { + "groupId": "W0000032", + "title": "케이크", + "subTitle": "Espresso", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000004034]_20220406125047195.jpg", + "category": "FOOD" + }, + { + "groupId": "W0000033", + "title": "샌드위치", + "subTitle": "Frappuccino", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000004005]_20220420080006656.jpg", + "category": "FOOD" + }, + { + "groupId": "W0000030", + "title": "머그", + "subTitle": "Cold Brew", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[11132362]_20220406154731279.jpg", + "category": "PRODUCT" + }, + { + "groupId": "W0000164", + "title": "글라스", + "subTitle": "Espresso", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[9300000003600]_20220413140543290.jpg", + "category": "PRODUCT" + }, + { + "groupId": "W0000031", + "title": "텀플러", + "subTitle": "Frappuccino", + "imagePath": "https://www.istarbucks.co.kr/upload/store/skuimg/2022/04/[11132329]_20220406144652838.jpg", + "category": "PRODUCT" + } + ] +} diff --git a/Starbucks/Starbucks/Resource/KoreaCash.mlmodel b/Starbucks/Starbucks/Resource/KoreaCash.mlmodel new file mode 100644 index 0000000..d0ede27 Binary files /dev/null and b/Starbucks/Starbucks/Resource/KoreaCash.mlmodel differ diff --git a/Starbucks/Starbucks/Resource/mockImage.png b/Starbucks/Starbucks/Resource/mockImage.png new file mode 100644 index 0000000..98b8a88 Binary files /dev/null and b/Starbucks/Starbucks/Resource/mockImage.png differ 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/.DS_Store b/Starbucks/Starbucks/Sources/.DS_Store new file mode 100644 index 0000000..b2afc82 Binary files /dev/null and b/Starbucks/Starbucks/Sources/.DS_Store differ diff --git a/Starbucks/Starbucks/Sources/API/Base/APIError.swift b/Starbucks/Starbucks/Sources/API/Base/APIError.swift new file mode 100644 index 0000000..cfaf066 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/APIError.swift @@ -0,0 +1,30 @@ +// +// APIError.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +enum APIError: Error { + case custom(message: String, debugMessage: String) + 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 .objectMapping(_, let response), + .statusCode(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 new file mode 100644 index 0000000..bdd3939 --- /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..b6c6505 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/Provider.swift @@ -0,0 +1,81 @@ +// +// 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 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 + 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> { + 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 { } + } + } +} diff --git a/Starbucks/Starbucks/Sources/API/Base/Response.swift b/Starbucks/Starbucks/Sources/API/Base/Response.swift new file mode 100644 index 0000000..27df968 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/Base/Response.swift @@ -0,0 +1,70 @@ +// +// 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 { + 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.statusCode(response: response)) + } + } + } +} diff --git a/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift new file mode 100644 index 0000000..e07c1f0 --- /dev/null +++ b/Starbucks/Starbucks/Sources/API/StarbucksTarget.swift @@ -0,0 +1,80 @@ +// +// StarbucksTarget.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation + +enum StarbucksTarget: BaseTarget { + case requestHome + case requestPromotion + case requestNews + case requestNotice + case requestProductInfo(_ id: String) + case requestProductImage(_ id: String) + case requestCategoryProduct(_ id: String) +} + +extension StarbucksTarget { + + var baseURL: URL? { + switch self { + case .requestHome: + return URL(string: "https://api.codesquad.kr/starbuckst") + case .requestPromotion, .requestProductInfo, .requestProductImage, .requestNews, .requestNotice, .requestCategoryProduct: + return URL(string: "https://www.starbucks.co.kr") + } + } + + var path: String? { + switch self { + case .requestHome: + return nil + 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" + case .requestCategoryProduct(let id): + return "/upload/json/menu/\(id).js" + } + } + + var parameter: [String: Any]? { + switch self { + case .requestHome, .requestNews, .requestNotice, .requestCategoryProduct: + return nil + case .requestPromotion: + return ["MENU_CD": "all"] + case .requestProductInfo(let id): + return ["product_cd": id] + case .requestProductImage(let id): + return ["PRODUCT_CD": id] + } + } + + var method: HTTPMethod { + switch self { + case .requestHome, .requestNews, .requestNotice, .requestCategoryProduct: + return .get + case .requestPromotion, .requestProductInfo, .requestProductImage : + return .post + } + } + + var content: HTTPContentType { + switch self { + case .requestHome: + return .json + case .requestPromotion, .requestProductImage, .requestNews, .requestProductInfo, .requestNotice, .requestCategoryProduct: + return .urlencode + } + } +} 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/Common/Camera/CameraSession.swift b/Starbucks/Starbucks/Sources/Common/Camera/CameraSession.swift new file mode 100644 index 0000000..78afb53 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/Camera/CameraSession.swift @@ -0,0 +1,92 @@ +// +// CameraSession.swift +// Starbucks +// +// Created by seongha shin on 2022/05/15. +// + +import AVFoundation +import CoreImage + +protocol CameraSessionDelegate: AnyObject { + func cameraCaptureOutput(didOutput sampleBuffer: CMSampleBuffer) +} + +class CameraSession: NSObject { + + class Config { + var preset: AVCaptureSession.Preset = .hd1280x720 + var queue = DispatchQueue(label: "com.cameraSession.lockQueue") + var formatType: OSType = kCVPixelFormatType_32BGRA + var cameraType: AVCaptureDevice.DeviceType = .builtInWideAngleCamera + var position: AVCaptureDevice.Position = .back + } + + weak var delegate: CameraSessionDelegate? + + private let captureSession = AVCaptureSession() + private let videoDataOutput = AVCaptureVideoDataOutput() + private var config = Config() + private var currentBuffer: CMSampleBuffer? + + func findCameraDevices(_ types: [AVCaptureDevice.DeviceType], position: AVCaptureDevice.Position) -> [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/View/PreviewView.swift b/Starbucks/Starbucks/Sources/Common/Camera/View/PreviewView.swift new file mode 100644 index 0000000..29904a7 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/Camera/View/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 new file mode 100644 index 0000000..df0b9b0 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Common/Container.swift @@ -0,0 +1,22 @@ +// +// 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 cameraRepository: CameraRepository = CameraRepositoryImpl() + + lazy var imageManager = ImageManager() + + lazy var userStore = UserStore() +} 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/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/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/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/Extension/NSMutableAttributedString+Extension.swift b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift new file mode 100644 index 0000000..8389c12 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Extension/NSMutableAttributedString+Extension.swift @@ -0,0 +1,50 @@ +// +// 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 + case paragraphStyle(_ style: NSMutableParagraphStyle) +} + +extension NSAttributedString { + static func create(_ string: String, options: [AttributedStringOption] = []) -> NSAttributedString { + 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 + case .paragraphStyle(let style): + attributes[.paragraphStyle] = style + } + } + 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/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/Extension/UIColor+Extension.swift b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift new file mode 100644 index 0000000..c379d9b --- /dev/null +++ b/Starbucks/Starbucks/Sources/Extension/UIColor+Extension.swift @@ -0,0 +1,14 @@ +// +// UIColor+Extension.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +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/Model/CategoryEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift new file mode 100644 index 0000000..9bcf939 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Model/CategoryEntity.swift @@ -0,0 +1,64 @@ +// +// CategoryEntity.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import Foundation + +struct Category: Codable { + let groups: [Group] +} + +extension Category { + + enum GroupType: String, Codable, CaseIterable { + case beverage = "BEVERAGE" + case food = "FOOD" + case product = "PRODUCT" + + var name: String { + switch self { + case .beverage: + return "음료" + case .food: + return "푸드" + case .product: + 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 { + let category: Category.GroupType + let groupId: String + let title: String + let subTitle: String + let imagePath: URL + } +} diff --git a/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift new file mode 100644 index 0000000..e3797ea --- /dev/null +++ b/Starbucks/Starbucks/Sources/Model/CategoryProductEntity.swift @@ -0,0 +1,34 @@ +// +// CategoryProductEntity.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/16. +// + +import Foundation + +struct CategoryProductEntity: Decodable { + let products: [Product] + + enum CodingKeys: String, CodingKey { + case products = "list" + } +} + +// MARK: - Product +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/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/Model/StarbucksEntity.swift b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift new file mode 100644 index 0000000..44552d9 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Model/StarbucksEntity.swift @@ -0,0 +1,172 @@ +// +// 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" + } + + var imageUrl: URL { + imageUploadPath.appendingPathComponent(thumbnail) + } + } +} + +extension StarbucksEntity { + struct Promotions: Decodable { + let list: [Promotion] + } + + struct Promotion: 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" + } + + var imageUrl: URL { + imageUploadPath.appendingPathComponent("/upload/promotion/" + thumbnail) + } + } +} + +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? + } + + 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 + } + } + + 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) + } + } +} + +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/.DS_Store b/Starbucks/Starbucks/Sources/Present/.DS_Store new file mode 100644 index 0000000..b2f4a6b Binary files /dev/null and b/Starbucks/Starbucks/Sources/Present/.DS_Store differ diff --git a/Starbucks/Starbucks/Sources/Present/Common/GradientView.swift b/Starbucks/Starbucks/Sources/Present/Common/GradientView.swift new file mode 100644 index 0000000..bc4fe79 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Common/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/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/Home/HomeViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift new file mode 100644 index 0000000..66eb2e3 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewController.swift @@ -0,0 +1,194 @@ +// +// MainViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import RxAppState +import RxSwift +import SnapKit +import UIKit + +class HomeViewController: UIViewController { + enum Constants { + static let stickyViewHeight = 50.0 + static let homeInfoViewHeight = 250.0 + static let bottomOffset = 150.0 + } + + private let scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.isPagingEnabled = false + scrollView.showsHorizontalScrollIndicator = true + scrollView.contentInsetAdjustmentBehavior = .never + scrollView.bounces = false + return scrollView + }() + + private let contentStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 20 + return stackView + }() + + private let homeInfoView: HomeInfoView = { + let view = HomeInfoView(stickViewHeight: Constants.stickyViewHeight) + return view + }() + + 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 + }() + + 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() + private var topSafeArea: CGFloat = 0 + + init(viewModel: HomeViewModelProtocol) { + 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().loadHome) + .disposed(by: disposeBag) + + rx.viewWillAppear + .withUnretained(self) + .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 + let window = UIApplication.shared.windows[0] + vc.topSafeArea = window.safeAreaInsets.top + }) + .disposed(by: disposeBag) + + viewModel.state().titleMessage + .bind(onNext: homeInfoView.setMessage) + .disposed(by: disposeBag) + + viewModel.state().presentProductDetailView + .withUnretained(self) + .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) + + Observable + .merge( + homeInfoView.tappedWhatsNewButton.asObservable(), + viewModel.state().presentWhatsNewListView.asObservable() + ) + .withUnretained(self) + .bind(onNext: { vc, _ in + let viewController = WhatsNewListViewController(viewModel: WhatsNewListViewModel()) + vc.navigationItem.backButtonTitle = "" + vc.navigationController?.pushViewController(viewController, animated: true) + }) + .disposed(by: disposeBag) + } + + private func attribute() { + scrollView.delegate = self + view.backgroundColor = .white + } + + private func layout() { + view.addSubview(scrollView) + view.addSubview(homeInfoView) + scrollView.addSubview(contentStackView) + contentStackView.addArrangedSubview(myRecommandMenuViewController.view) + contentStackView.addArrangedSubview(mainEventViewController.view) + contentStackView.addArrangedSubview(whatsNewViewController.view) + contentStackView.addArrangedSubview(timeRecommandMenuViewController.view) + + contentStackView.setCustomSpacing(Constants.stickyViewHeight, after: homeInfoView) + + scrollView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.bottom.equalToSuperview() + } + + scrollView.contentLayoutGuide.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.bottom.equalTo(contentStackView).offset(Constants.bottomOffset) + } + + contentStackView.snp.makeConstraints { + $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 new file mode 100644 index 0000000..a35f357 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/HomeViewModel.swift @@ -0,0 +1,131 @@ +// +// MainViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import Foundation +import RxRelay +import RxSwift + +protocol HomeViewModelAction { + var loadHome: PublishRelay { get } +} + +protocol HomeViewModelState { + var titleMessage: PublishRelay { get } + var presentProductDetailView: PublishRelay { get } + var presentWhatsNewListView: PublishRelay { get } +} + +protocol HomeViewModelBinding { + func action() -> HomeViewModelAction + func state() -> HomeViewModelState +} + +protocol HomeViewModelProperty { + var whatsNewViewModel: WhatsNewViewModelProtocol { get } + var mainEventViewModel: MainEventViewModelProtocol { get } + var recommandMenuViewModel: RecommandMenuViewModelProtocol { get } + var timeRecommandMenuViewModel: RecommandMenuViewModelProtocol { get } +} + +typealias HomeViewModelProtocol = HomeViewModelBinding & HomeViewModelProperty + +class HomeViewModel: HomeViewModelBinding, HomeViewModelProperty, HomeViewModelAction, HomeViewModelState { + func action() -> HomeViewModelAction { self } + + let loadHome = PublishRelay() + + func state() -> HomeViewModelState { self } + + let titleMessage = PublishRelay() + let presentProductDetailView = PublishRelay() + let presentWhatsNewListView = PublishRelay() + + 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() + private var homeData: StarbucksEntity.Home? + + init() { + let requestHome = action().loadHome + .withUnretained(self) + .flatMapLatest { model, _ in + model.starbucksRepository.requestHome() + } + .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) + .disposed(by: disposeBag) + + requestHome + .compactMap { $0.value?.displayName } + .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 + .compactMap { $0.value?.nowRecommand.products } + .bind(to: timeRecommandMenuViewModel.action().loadedProducts) + .disposed(by: disposeBag) + + requestHome + .compactMap { $0.value?.mainEvent } + .bind(to: mainEventViewModel.action().loadedEvent) + .disposed(by: disposeBag) + + Observable + .merge( + requestHome.compactMap { $0.error } + ) + .bind(onNext: { _ in + }) + .disposed(by: disposeBag) + + 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) + + 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 new file mode 100644 index 0000000..6c68ac1 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/HomeInfoView/HomeInfoView.swift @@ -0,0 +1,147 @@ +// +// HomeInfoView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import RxRelay +import RxSwift +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 + private let disposeBag = DisposeBag() + + let tappedWhatsNewButton = PublishRelay() + + init(stickViewHeight: CGFloat) { + self.stickViewHeight = stickViewHeight + super.init(frame: .zero) + bind() + attribute() + layout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func bind() { + whatsNewButton.rx.tap + .bind(to: tappedWhatsNewButton) + .disposed(by: disposeBag) + } + + 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/MainEvent/MainEventViewController.swift b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift new file mode 100644 index 0000000..1f30b6e --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewController.swift @@ -0,0 +1,83 @@ +// +// 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 = .black.withAlphaComponent(0.5) + 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() + + 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) + + 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) + } + + eventImageView.snp.makeConstraints { + $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 new file mode 100644 index 0000000..ad7b636 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/MainEvent/MainEventViewModel.swift @@ -0,0 +1,47 @@ +// +// MainEventViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import Foundation +import RxRelay +import RxSwift + +protocol MainEventViewModelAction { + var loadedEvent: PublishRelay { get } + var tappedEvent: 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() + var tappedEvent = PublishRelay() + + func state() -> MainEventViewModelState { self } + + let loadedMainEventImage = PublishRelay() + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + init() { + loadedEvent + .map { $0.imageUrl } + .bind(to: loadedMainEventImage) + .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 new file mode 100644 index 0000000..5461810 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuCellView.swift @@ -0,0 +1,81 @@ +// +// SuggestionMenuCellView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import RxSwift +import UIKit + +class RecommandMenuCellView: UICollectionViewCell { + static let identifier = "RecommandMenuCellView" + + private let thumbnailView: UIImageView = { + let imageView = UIImageView() + imageView.backgroundColor = .brown + imageView.clipsToBounds = true + imageView.layer.cornerRadius = RecommandMenuViewController.Constants.cellSize.width / 2 + return imageView + }() + + private let nameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 13) + label.textAlignment = .center + label.numberOfLines = 2 + label.lineBreakMode = .byCharWrapping + 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(nameLabel) + + thumbnailView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(snp.width) + } + + nameLabel.snp.makeConstraints { + $0.top.equalTo(thumbnailView.snp.bottom).offset(13) + $0.leading.trailing.equalToSuperview() + } + } + + func setName(_ name: NSMutableAttributedString) { + nameLabel.backgroundColor = .white + nameLabel.attributedText = 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) + } + + 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 new file mode 100644 index 0000000..061bd38 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuDataSource.swift @@ -0,0 +1,60 @@ +// +// SuggestionMenuDataSource.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import Foundation +import UIKit + +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 + } + + func updateProductImages(_ images: [[StarbucksEntity.ProductImageInfo]]) { + self.productimages = images + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + 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 + + 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 new file mode 100644 index 0000000..7ddc2ee --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewController.swift @@ -0,0 +1,122 @@ +// +// SuggestionMenuView.swift +// Starbucks +// +// Created by seongha shin on 2022/05/11. +// + +import RxSwift +import UIKit + +enum RecommandMenuType { + case myMenu, thisTime +} + +class RecommandMenuViewController: UIViewController { + enum Constants { + static let cellSize = CGSize(width: 130, height: 180) + } + + private let titleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .left + label.font = .systemFont(ofSize: 24, weight: .bold) + label.textColor = .black + label.backgroundColor = .black.withAlphaComponent(0.1) + return label + }() + + private let menuCollectionView: 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(RecommandMenuCellView.self, forCellWithReuseIdentifier: RecommandMenuCellView.identifier) + collectionView.showsHorizontalScrollIndicator = false + return collectionView + }() + + private let viewModel: RecommandMenuViewModelProtocol + private let disposeBag = DisposeBag() + private let dataSource: RecommandMenuDataSource + + init(type: RecommandMenuType, viewModel: RecommandMenuViewModelProtocol) { + self.viewModel = viewModel + self.dataSource = RecommandMenuDataSource(type: type) + 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() { + 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.backgroundColor = .white + vc.titleLabel.attributedText = title + }) + .disposed(by: disposeBag) + } + + private func attribute() { + menuCollectionView.dataSource = dataSource + menuCollectionView.delegate = self + menuCollectionView.reloadData() + } + + private func layout() { + view.addSubview(titleLabel) + view.addSubview(menuCollectionView) + + view.snp.makeConstraints { + $0.bottom.equalTo(menuCollectionView) + } + + titleLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(10) + $0.leading.equalToSuperview().offset(20) + $0.trailing.equalToSuperview() + } + + menuCollectionView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(20) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(Constants.cellSize.height) + } + } +} + +extension RecommandMenuViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + 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 new file mode 100644 index 0000000..11070ef --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/Recommand/RecommandMenuViewModel.swift @@ -0,0 +1,70 @@ +// +// 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 selectedProduct: 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 selectedProduct = 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 + .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) + + 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) + } +} 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..8beb897 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewCellView.swift @@ -0,0 +1,73 @@ +// +// 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(url: URL) { + 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..9af77a4 --- /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.Promotion] = [] + + func updateEvents(_ events: [StarbucksEntity.Promotion]) { + 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(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 new file mode 100644 index 0000000..41d68a5 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewController.swift @@ -0,0 +1,122 @@ +// +// 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: 220, 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().updateEvents + .bind(onNext: dataSource.updateEvents) + .disposed(by: disposeBag) + + viewModel.state().reloadEvents + .bind(onNext: eventCollectionView.reloadData) + .disposed(by: disposeBag) + + seeAllButton.rx.tap + .bind(to: viewModel.action().tappedSeeAllButton) + .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 + } + + 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 new file mode 100644 index 0000000..6035e1c --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Home/View/WhatsNew/WhatsNewViewModel.swift @@ -0,0 +1,69 @@ +// +// WhatsNewViewModel.swift +// Starbucks +// +// Created by seongha shin on 2022/05/12. +// + +import Foundation +import RxRelay +import RxSwift + +protocol WhatsNewViewModelAction { + var loadEvent: PublishRelay { get } + var selectedEvent: PublishRelay { get } + var tappedSeeAllButton: PublishRelay { get } +} + +protocol WhatsNewViewModelState { + var updateEvents: PublishRelay<[StarbucksEntity.Promotion]> { get } + var reloadEvents: PublishRelay { get } +} + +protocol WhatsNewViewModelBinding { + func action() -> WhatsNewViewModelAction + func state() -> WhatsNewViewModelState +} + +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 updateEvents = PublishRelay<[StarbucksEntity.Promotion]>() + let reloadEvents = PublishRelay() + + @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 } + .do { [weak self] list in self?.updateEvents.accept(list) } + .map { _ in } + .bind(to: reloadEvents) + .disposed(by: disposeBag) + + Observable + .merge( + requestEvent.compactMap { $0.error } + ) + .bind(onNext: { _ in + }) + .disposed(by: disposeBag) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift new file mode 100644 index 0000000..ef3c120 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/CategoryTableViewCell.swift @@ -0,0 +1,100 @@ +// +// OrderTableViewCell.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import RxSwift +import UIKit + +class CategoryTableViewCell: UITableViewCell { + + static var identifier: String { "\(self)" } + + private let menuImageView: UIImageView = { + 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 + }() + + 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 + }() + + @Inject(\.imageManager) private var imageManager: ImageManager + private let disposeBag = DisposeBag() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + layout() + selectionStyle = .none + } + + @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) + + 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) + } + + 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 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/OrderCategoryViewController.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift new file mode 100644 index 0000000..3a2df25 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderCategoryViewController.swift @@ -0,0 +1,181 @@ +// +// OrderCategoryViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import RxAppState +import RxCocoa +import RxSwift +import SnapKit +import UIKit + +class OrderCategoryViewController: UIViewController { + + let test = UIView() + + private let tableSectionHeaderView: UIView = { + let view = UIView() + 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 + stackView.distribution = .equalSpacing + stackView.spacing = 10 + return stackView + }() + + private let categoryButtons: [UIButton] = { + 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 + } + }() + + private let categoryViewBar: UIView = { + let view = UIView() + view.backgroundColor = .lightGray + return view + }() + + private var tableViewDataSource = OrderTableViewDataSource() + 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().loadCategory) + .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] + 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) + + viewModel.state().updateList + .bind(onNext: tableViewDataSource.update) + .disposed(by: disposeBag) + + viewModel.state().reloadList + .bind(onNext: tableView.reloadData) + .disposed(by: disposeBag) + + viewModel.state().selectedCategory + .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 + .map { _ in index } + .bind(to: viewModel.action().tappedCategory) + .disposed(by: disposeBag) + } + + viewModel.state().selectedSubCategory + .withUnretained(self) + .bind(onNext: { model, subCategory in + let viewModel = OrderListViewModel(subCategory: subCategory.groupId, title: subCategory.title) + let orderListVC = OrderListViewController(viewModel: viewModel) + model.navigationItem.backButtonTitle = "" + model.navigationController?.pushViewController(orderListVC, animated: true) + }) + .disposed(by: disposeBag) + } + + private func attribute() { + title = "Order" + view.backgroundColor = .white + tableView.delegate = self + tableView.dataSource = tableViewDataSource + } + + private func layout() { + view.addSubview(tableView) + + tableSectionHeaderView.addSubview(categoryStackView) + tableSectionHeaderView.addSubview(categoryViewBar) + categoryButtons.forEach { + categoryStackView.addArrangedSubview($0) + } + + categoryStackView.snp.makeConstraints { + $0.top.bottom.equalToSuperview() + $0.trailing.equalTo(categoryButtons[categoryButtons.count - 1]) + $0.leading.equalToSuperview().offset(20) + } + + categoryViewBar.snp.makeConstraints { + $0.leading.trailing.bottom.equalToSuperview() + $0.height.equalTo(1) + } + + tableView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(view.safeAreaLayoutGuide) + } + } +} + +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 new file mode 100644 index 0000000..31aa546 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDataSource.swift @@ -0,0 +1,38 @@ +// +// OrderTableViewDataSource.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import RxRelay +import RxSwift +import UIKit + +class OrderTableViewDataSource: NSObject, UITableViewDataSource { + + private var menus: [Category.Group] = [] + + func update(menus: [Category.Group]) { + self.menus = menus + } + + func numberOfSections(in tableView: UITableView) -> Int { + 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + 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].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/OrderTableViewDelegate.swift b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift new file mode 100644 index 0000000..6c595ed --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderTableViewDelegate.swift @@ -0,0 +1,24 @@ +// +// OrderTableViewDelegate.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +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) { +// +// selectedCellIndexonNext(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..542bc4f --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderCategory/OrderViewModel.swift @@ -0,0 +1,109 @@ +// +// OrderViewModel.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/09. +// + +import Foundation +import RxRelay +import RxSwift + +protocol OrderViewModelAction { + var loadCategory: PublishRelay { get } + var tappedCategory: BehaviorRelay { get } + var tappedMenu: PublishRelay { get } +} + +protocol OrderViewModelState { + var updateList: PublishRelay<[Category.Group]> { get } + var reloadList: PublishRelay { get } + var selectedCategory: PublishRelay { get } + var selectedSubCategory: PublishRelay { get } +} + +protocol OrderViewModelBinding { + func action() -> OrderViewModelAction + func state() -> OrderViewModelState +} + +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 = BehaviorRelay(value: Contants.firstCategory.index) + let tappedMenu = PublishRelay() + + func state() -> OrderViewModelState { self } + + 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.Group]] = [[]] + + init() { + + let requestCategory = action().loadCategory + .withUnretained(self) + .flatMapLatest { model, _ in + model.starbucksRepository.requestCategory() + } + .share() + + requestCategory + .compactMap { result in result.value } + .map { groups in + groups.reduce(into: [[Category.Group]].init(repeating: [], count: 3)) { category, group in + category[group.category.index].append(group) + } + } + .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 } + ) + .bind(onNext: { + //TODO: error 처리 + }) + .disposed(by: disposeBag) + + tappedCategory + .withUnretained(self) + .map { model, index in model.categoryMenu[index] } + .withUnretained(self) + .do { model, groups in model.updateList.accept(groups) } + .map { _ in } + .bind(to: reloadList) + .disposed(by: disposeBag) + + tappedMenu + .withUnretained(self) + .map { model, index in + model.categoryMenu[model.tappedCategory.value][index] + } + .bind(to: selectedSubCategory) + .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..0ce4d6c --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderDetail/OrderDetailViewController.swift @@ -0,0 +1,215 @@ +// +// OrderDetailViewController.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/10. +// + +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) + 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?.isNavigationBarHidden = false + vc.tabBarController?.tabBar.isHidden = true + }) + .disposed(by: disposeBag) + } + + private func attribute() { + view.backgroundColor = .white + } + + 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/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/OrderListTableViewDataSource.swift b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift new file mode 100644 index 0000000..d26a90c --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListTableViewDataSource.swift @@ -0,0 +1,35 @@ +// +// OrderListTableViewDataSource.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import RxRelay +import RxSwift +import UIKit + +class OrderListTableViewDataSource: NSObject, UITableViewDataSource { + + private var products: [Product] = [] + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + products.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 = 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/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 new file mode 100644 index 0000000..5fbc7ac --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewController.swift @@ -0,0 +1,109 @@ +// +// DetailOrderViewController.swift +// Starbucks +// +// Created by 김상혁 on 2022/05/10. +// + +import RxAppState +import RxCocoa +import RxSwift +import SnapKit +import UIKit + +class OrderListViewController: UIViewController { + + private let tableView = UITableView() + private let tableViewHandler = OrderListTableViewDelegate() + private let viewModel: OrderListViewModelProtocol + private var tableViewDatasource = OrderListTableViewDataSource() + + private let categoryLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 28, weight: .bold) + return label + }() + + private let disposeBag = DisposeBag() + + init(viewModel: OrderListViewModelProtocol) { + 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().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) + + viewModel.state().updatedList + .bind(onNext: tableViewDatasource.update) + .disposed(by: disposeBag) + + 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() { + 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..a735bcf --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/OrderList/OrderListViewModel.swift @@ -0,0 +1,82 @@ +// +// OrderListViewModel.swift +// Starbucks +// +// Created by YEONGJIN JANG on 2022/05/17. +// + +import Foundation +import RxRelay +import RxSwift + +protocol OrderListViewModelAction { + var loadProductCode: PublishRelay { get } + var loadList: PublishRelay { get } + var updateTitle: PublishRelay { get } +} + +protocol OrderListViewModelState { + var loadedProductCode: PublishRelay { get } + var updatedList: PublishRelay<[Product]> { get } + var reloadedList: PublishRelay { get } + var updatedTitle: PublishRelay { get } +} + +protocol OrderListViewModelBinding { + func action() -> OrderListViewModelAction + func state() -> OrderListViewModelState +} + +typealias OrderListViewModelProtocol = OrderListViewModelBinding + +class OrderListViewModel: OrderListViewModelAction, OrderListViewModelState, OrderListViewModelBinding { + + @Inject(\.starbucksRepository) private var starbucksRepository: StarbucksRepository + private let disposeBag = DisposeBag() + + func action() -> OrderListViewModelAction { self } + + let loadProductCode = PublishRelay() + let loadList = PublishRelay() + let updateTitle = PublishRelay() + + func state() -> OrderListViewModelState { self } + + var loadedProductCode = PublishRelay() + let updatedList = PublishRelay<[Product]>() + let reloadedList = PublishRelay() + let updatedTitle = PublishRelay() + + 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(subCategory).asObservable() + } + .share() + + requestProducts + .compactMap { $0.value?.products } + .withUnretained(self) + .do { model, products in + model.updatedList.accept(products) + } + .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) + } +} diff --git a/Starbucks/Starbucks/Sources/Present/Pay/.DS_Store b/Starbucks/Starbucks/Sources/Present/Pay/.DS_Store new file mode 100644 index 0000000..f7affc9 Binary files /dev/null and b/Starbucks/Starbucks/Sources/Present/Pay/.DS_Store differ diff --git a/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift new file mode 100644 index 0000000..aea1186 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/Pay/PayViewController.swift @@ -0,0 +1,166 @@ +// +// PayViewController.swift +// Starbucks +// +// Created by seongha shin on 2022/05/09. +// + +import AVFoundation +import RxSwift +import UIKit + +class PayViewController: UIViewController { + + 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 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.viewWillAppear + .withUnretained(self) + .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) + + 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() { + title = "Pay" + navigationItem.rightBarButtonItem = tappedAddCard + } + + private func layout() { + view.addSubview(scrollView) + view.addSubview(chargeView) + + scrollView.addSubview(contentStackView) + contentStackView.addArrangedSubview(cardListViewController.view) + chargeView.addSubview(previewView) + chargeView.addSubview(captureButton) + + 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() + } + + 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) + } + } +} 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/.DS_Store b/Starbucks/Starbucks/Sources/Present/Pay/View/.DS_Store new file mode 100644 index 0000000..20888d8 Binary files /dev/null and b/Starbucks/Starbucks/Sources/Present/Pay/View/.DS_Store differ 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/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) + } +} 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..f25c6df --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/TabBar/StarbucksViewController.swift @@ -0,0 +1,37 @@ +// +// 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 = .starbuckGreen + tabBar.unselectedItemTintColor = .grey145 + setUpTabBar() + } + + private func setUpTabBar() { + let homeViewController = UINavigationController(rootViewController: HomeViewController(viewModel: HomeViewModel())) + homeViewController.tabBarItem.title = "Home" + homeViewController.tabBarItem.image = UIImage(named: "ic_temp") + + let payViewController = UINavigationController(rootViewController: PayViewController(viewModel: PayViewModel())) + payViewController.tabBarItem.title = "Pay" + payViewController.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, orderNavigationController + ] + } +} 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..450c8ae --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewController.swift @@ -0,0 +1,90 @@ +// +// 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") + } + + deinit { + Log.info("Deinit WhatsNewListViewController") + } + + private func bind() { + rx.viewDidLoad + .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) + + viewModel.state().reloadEvents + .bind(onNext: tableView.reloadData) + .disposed(by: disposeBag) + } + + private func attribute() { + title = "What's New" + view.backgroundColor = .white + tableView.dataSource = dataSource + } + + private func layout() { + view.addSubview(tableView) + + tableView.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide) + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(view.safeAreaLayoutGuide) + } + } +} diff --git a/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift new file mode 100644 index 0000000..4c16b86 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Present/WhatsNewList/WhatsNewListViewModel.swift @@ -0,0 +1,60 @@ +// +// 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 updateEvents: PublishRelay<[StarbucksEntity.Event]> { get } + var reloadEvents: PublishRelay { 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 updateEvents = PublishRelay<[StarbucksEntity.Event]>() + let reloadEvents = PublishRelay() + + @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 }) } + .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/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..06f1a40 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepository.swift @@ -0,0 +1,20 @@ +// +// StarbucksRepository.swift +// Starbucks +// +// Created by seongha shin on 2022/05/10. +// + +import Foundation +import RxSwift + +protocol StarbucksRepository { + func requestHome() -> Single> + func requestEvent() -> Single> + func requestDetail(_ id: String) -> Single> + func requestDetailImage(_ id: String) -> Single> + 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 new file mode 100644 index 0000000..a1064e2 --- /dev/null +++ b/Starbucks/Starbucks/Sources/Repository/Starbucks/StarbucksRepositoryImpl.swift @@ -0,0 +1,74 @@ +// +// 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(.requestPromotion) + .map(StarbucksEntity.Promotions.self) + } + + func requestDetail(_ id: String) -> Single> { + provider + .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"), + let data = try? Data(contentsOf: url) else { + observer(.success(.failure(.unowned))) + return Disposables.create { } + } + + 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 { } + } + } + + func requestNews() -> Single> { + provider + .request(.requestNews) + .map(StarbucksEntity.Events.self) + } + + func requestNotice() -> Single> { + provider + .request(.requestNotice) + .map(StarbucksEntity.Events.self) + } +} + +extension StarbucksRepositoryImpl { + func requestCategoryProduct(_ id: String) -> Single> { + provider + .request(.requestCategoryProduct(id)) + .map(CategoryProductEntity.self) + } +} diff --git a/Starbucks/Starbucks/Sources/SceneDelegate.swift b/Starbucks/Starbucks/Sources/SceneDelegate.swift new file mode 100644 index 0000000..12ec3a2 --- /dev/null +++ b/Starbucks/Starbucks/Sources/SceneDelegate.swift @@ -0,0 +1,18 @@ +// +// 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) { + guard let scene = (scene as? UIWindowScene) else { return } + self.window = RootWindow(windowScene: scene) + window?.makeKeyAndVisible() + } +} 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)) + } +} 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("사용자 테스트") { + + } + } +}