├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── run_tests.yml
│ └── swiftlint.yml
├── .gitignore
├── LICENSE
├── NativeAppTemplate.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcshareddata
│ └── xcschemes
│ └── NativeAppTemplate.xcscheme
├── NativeAppTemplate
├── .swiftlint.yml
├── App.swift
├── AppSingletons.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon_free 1.png
│ │ ├── icon_free 2.png
│ │ └── icon_free.png
│ ├── Colours
│ │ ├── Backgrounds
│ │ │ ├── Contents.json
│ │ │ ├── backgroundColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── cardBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── coloredPrimaryBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── coloredPrimaryForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── coloredSecondaryBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── coloredSecondaryForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── failureBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── successBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ └── successSecondaryForeground.colorset
│ │ │ │ └── Contents.json
│ │ ├── Button
│ │ │ ├── Contents.json
│ │ │ ├── coloredPrimaryButtonForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── coloredSecondaryButtonForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── destructiveButtonForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── failureSecondaryForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── primaryButtonForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ └── secondaryButtonForeground.colorset
│ │ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Snackbar
│ │ │ ├── Contents.json
│ │ │ ├── error.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── snackText.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── success.colorset
│ │ │ │ └── Contents.json
│ │ │ └── warning.colorset
│ │ │ │ └── Contents.json
│ │ ├── Tags
│ │ │ ├── Contents.json
│ │ │ ├── completedTagBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── completedTagBorder.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── completedTagForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── customerScannedTagBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── customerScannedTagBorder.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── customerScannedTagForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── idlingTagBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── idlingTagBorder.colorset
│ │ │ │ └── Contents.json
│ │ │ └── idlingTagForeground.colorset
│ │ │ │ └── Contents.json
│ │ ├── Text
│ │ │ ├── Contents.json
│ │ │ ├── coloredPrimaryFootnoteText.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── coloredSecondaryFootnoteText.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── contentText.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── secondaryText.colorset
│ │ │ │ └── Contents.json
│ │ │ └── titleText.colorset
│ │ │ │ └── Contents.json
│ │ ├── Write to Tag
│ │ │ ├── Contents.json
│ │ │ ├── customerBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── customerForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── lockBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── lockForeground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── serverBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ └── serverForeground.colorset
│ │ │ │ └── Contents.json
│ │ ├── accent.colorset
│ │ │ └── Contents.json
│ │ ├── alarm.colorset
│ │ │ └── Contents.json
│ │ └── lightestAccent.colorset
│ │ │ └── Contents.json
│ ├── Contents.json
│ ├── Logo
│ │ ├── Contents.json
│ │ └── logo.imageset
│ │ │ ├── Contents.json
│ │ │ └── logo_white.svg
│ └── Onboarding
│ │ ├── Contents.json
│ │ ├── onboarding1.imageset
│ │ ├── Contents.json
│ │ ├── overview1~universal@1x.png
│ │ ├── overview1~universal@2x.png
│ │ └── overview1~universal@3x.png
│ │ ├── onboarding10.imageset
│ │ ├── Contents.json
│ │ ├── overview9~universal@1x.png
│ │ ├── overview9~universal@2x.png
│ │ └── overview9~universal@3x.png
│ │ ├── onboarding11.imageset
│ │ ├── Contents.json
│ │ ├── overview9_phone_customer1~universal@1x.png
│ │ ├── overview9_phone_customer1~universal@2x.png
│ │ └── overview9_phone_customer1~universal@3x.png
│ │ ├── onboarding12.imageset
│ │ ├── Contents.json
│ │ ├── overview13~universal@1x.png
│ │ ├── overview13~universal@2x.png
│ │ └── overview13~universal@3x.png
│ │ ├── onboarding13.imageset
│ │ ├── Contents.json
│ │ ├── overview14~universal@1x.png
│ │ ├── overview14~universal@2x.png
│ │ └── overview14~universal@3x.png
│ │ ├── onboarding1Slim.imageset
│ │ ├── Contents.json
│ │ ├── overview1_slim~universal@1x.png
│ │ ├── overview1_slim~universal@2x.png
│ │ └── overview1_slim~universal@3x.png
│ │ ├── onboarding2.imageset
│ │ ├── Contents.json
│ │ ├── overview2~universal@1x.png
│ │ ├── overview2~universal@2x.png
│ │ └── overview2~universal@3x.png
│ │ ├── onboarding3.imageset
│ │ ├── Contents.json
│ │ ├── overview6~universal@1x.png
│ │ ├── overview6~universal@2x.png
│ │ └── overview6~universal@3x.png
│ │ ├── onboarding4.imageset
│ │ ├── Contents.json
│ │ ├── overview6_phone_customer2~universal@1x.png
│ │ ├── overview6_phone_customer2~universal@2x.png
│ │ └── overview6_phone_customer2~universal@3x.png
│ │ ├── onboarding5.imageset
│ │ ├── Contents.json
│ │ ├── overview7~universal@1x.png
│ │ ├── overview7~universal@2x.png
│ │ └── overview7~universal@3x.png
│ │ ├── onboarding6.imageset
│ │ ├── Contents.json
│ │ ├── overview8~universal@1x.png
│ │ ├── overview8~universal@2x.png
│ │ └── overview8~universal@3x.png
│ │ ├── onboarding7.imageset
│ │ ├── Contents.json
│ │ ├── overview8_phone_server2~universal@1x.png
│ │ ├── overview8_phone_server2~universal@2x.png
│ │ └── overview8_phone_server2~universal@3x.png
│ │ ├── onboarding8.imageset
│ │ ├── Contents.json
│ │ ├── overview8_phone_server3~universal@1x.png
│ │ ├── overview8_phone_server3~universal@2x.png
│ │ └── overview8_phone_server3~universal@3x.png
│ │ └── onboarding9.imageset
│ │ ├── Contents.json
│ │ ├── overview8_2~universal@1x.png
│ │ ├── overview8_2~universal@2x.png
│ │ └── overview8_2~universal@3x.png
├── Constants.swift
├── Data
│ ├── DataManager.swift
│ ├── DataState.swift
│ ├── Repositories
│ │ ├── AccountPasswordRepository.swift
│ │ ├── ItemTagRepository.swift
│ │ └── ShopRepository.swift
│ └── ViewModels
│ │ └── TabViewModel.swift
├── Extensions
│ ├── Bundle+Extensions.swift
│ ├── Date+Extensions.swift
│ ├── DateFormatter+Extensions.swift
│ ├── String+Extensions.swift
│ ├── UIApplication+DismissKeyboard.swift
│ ├── UIImage+Extentions.swift
│ └── View+Extensions.swift
├── Info.plist
├── Logging
│ └── Logger.swift
├── Login
│ ├── LoginRepository.swift
│ ├── OnboardingRepository.swift
│ ├── SessionRequest.swift
│ ├── SessionsService.swift
│ ├── SignUpRepository.swift
│ ├── SignUpRequest.swift
│ └── SignUpService.swift
├── Models
│ ├── CompleteScanResult.swift
│ ├── ItemTag.swift
│ ├── ItemTagData.swift
│ ├── ItemTagInfoFromNdefMessage.swift
│ ├── ItemTagState.swift
│ ├── ItemTagType.swift
│ ├── MainTab.swift
│ ├── Onboarding.swift
│ ├── ScanResultError.swift
│ ├── ScanState.swift
│ ├── ScrollToTopID.swift
│ ├── SendConfirmation.swift
│ ├── SendResetPassword.swift
│ ├── Shop.swift
│ ├── Shopkeeper.swift
│ ├── ShowTagInfoScanResult.swift
│ ├── SignUp.swift
│ └── UpdatePassword.swift
├── NFCManager.swift
├── NativeAppTemplate.entitlements
├── Networking
│ ├── Adapters
│ │ ├── DataCacheUpdate.swift
│ │ ├── EntityAdapter.swift
│ │ └── EntityAdapters
│ │ │ ├── ItemTagAdapter.swift
│ │ │ ├── ShopAdapter.swift
│ │ │ ├── ShopkeeperAdapter.swift
│ │ │ └── ShopkeeperSignInAdapter.swift
│ ├── JSONAPI
│ │ ├── JSONAPIDocument.swift
│ │ ├── JSONAPIError.swift
│ │ ├── JSONAPIErrorSource.swift
│ │ ├── JSONAPIRelationship.swift
│ │ └── JSONAPIResource.swift
│ ├── Network
│ │ ├── NativeAppTemplateAPI.swift
│ │ └── NativeAppTemplateEnvironment.swift
│ ├── Requests
│ │ ├── AccountPasswordRequest.swift
│ │ ├── ItemTagsRequest.swift
│ │ ├── MeRequest.swift
│ │ ├── Parameters.swift
│ │ ├── PermissionsRequest.swift
│ │ ├── Request.swift
│ │ └── ShopsRequest.swift
│ └── Services
│ │ ├── AccountPasswordService.swift
│ │ ├── ItemTagsService.swift
│ │ ├── MeService.swift
│ │ ├── PermissionsService.swift
│ │ ├── Service.swift
│ │ └── ShopsService.swift
├── Persistence
│ └── KeychainStore
│ │ ├── KeychainStore.swift
│ │ ├── LoggedInShopkeeper.swift
│ │ └── LoggedInShopkeeperKeychainStore.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── PrivacyInfo.xcprivacy
├── Sessions
│ ├── SessionController+States.swift
│ ├── SessionController.swift
│ └── Shopkeeper+Backdoor.swift
├── Styleguide
│ ├── Color+Extensions.swift
│ ├── Font+Extensions.swift
│ ├── Inter
│ │ ├── Inter-Bold.ttf
│ │ └── Inter-Medium.ttf
│ ├── UIColor+Extensions.swift
│ └── UIFont+Extensions.swift
├── TimeZoneData.swift
├── UI
│ ├── App Root
│ │ ├── AcceptPrivacyView.swift
│ │ ├── AcceptTermsView.swift
│ │ ├── AppTabView.swift
│ │ ├── ForgotPasswordView.swift
│ │ ├── MainView.swift
│ │ ├── MessageBarView.swift
│ │ ├── OnboardingView.swift
│ │ ├── PermissionsLoadingView.swift
│ │ ├── ResendConfirmationInstructionsView.swift
│ │ ├── SignInEmailAndPasswordView.swift
│ │ ├── SignUpOrSignInView.swift
│ │ ├── SignUpView.swift
│ │ └── SnackbarView.swift
│ ├── Empty States
│ │ ├── ErrorView.swift
│ │ ├── LoadingView.swift
│ │ ├── NeedAppUpdatesView.swift
│ │ └── OfflineView.swift
│ ├── Scan
│ │ ├── CompleteScanResultView.swift
│ │ ├── ScanView.swift
│ │ └── ShowTagInfoScanResultView.swift
│ ├── Settings
│ │ ├── PasswordEditView.swift
│ │ ├── SettingsView.swift
│ │ └── ShopkeeperEditView.swift
│ ├── Shared
│ │ ├── MainButtonView.swift
│ │ └── Tags
│ │ │ ├── CompletedTag.swift
│ │ │ ├── CustomerScannedTag.swift
│ │ │ ├── IdlingTagView.swift
│ │ │ └── TagView.swift
│ ├── Shop Detail
│ │ ├── ShopDetailCardView.swift
│ │ └── ShopDetailView.swift
│ ├── Shop List
│ │ ├── ItemTag Detail
│ │ │ ├── ItemTagDetailView.swift
│ │ │ └── ItemTagEditView.swift
│ │ ├── ItemTag List
│ │ │ ├── ItemTagCreateView.swift
│ │ │ ├── ItemTagListCardView.swift
│ │ │ └── ItemTagListView.swift
│ │ ├── ShopCreateView.swift
│ │ ├── ShopListCardView.swift
│ │ └── ShopListView.swift
│ ├── Shop Settings
│ │ ├── NumberTagsWebpageListView.swift
│ │ ├── ShopBasicSettingsView.swift
│ │ └── ShopSettingsView.swift
│ └── UIKit
│ │ └── MailView.swift
└── Utilities
│ ├── ImageSaver.swift
│ ├── MessageBus.swift
│ ├── QRCodeGenerator.swift
│ └── Utility.swift
├── NativeAppTemplateTests
├── Models
│ └── ShopkeeperTest.swift
└── Networking
│ └── Adapters
│ ├── ItemTagAdapterTest.swift
│ ├── ShopAdapterTest.swift
│ ├── ShopkeeperAdapterTest.swift
│ └── ShopkeeperSignInAdapterTest.swift
├── README.md
├── SECURITY.md
├── SampleCode.xcconfig
└── docs
└── images
├── nfc.gif
├── organization.gif
├── overview_after.png
├── overview_before.png
├── screenshots.png
└── screenshots_nfc.png
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ### Steps to reproduce
7 |
8 |
9 |
10 |
11 |
12 | ### Expected behavior
13 |
14 |
15 |
16 | ### Actual behavior
17 |
18 |
19 |
20 |
21 | ### Environment
22 |
23 |
24 |
25 | * iOS Version:
26 | * NativeAppTemplate App Version:
27 | * Device:
28 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/run_tests.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 | on:
3 | pull_request:
4 |
5 | jobs:
6 | build:
7 | runs-on: macos-15
8 | steps:
9 | - uses: maxim-lobanov/setup-xcode@v1
10 | with:
11 | xcode-version: '16.2.0'
12 | - uses: actions/checkout@v3
13 | - name: Unit Tests
14 | run: xcodebuild -project NativeAppTemplate.xcodeproj -scheme "NativeAppTemplate" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2' test
15 |
--------------------------------------------------------------------------------
/.github/workflows/swiftlint.yml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/swiftlint.yml'
7 | - 'NativeAppTemplate/.swiftlint.yml'
8 | - 'NativeAppTemplate/**/*.swift'
9 |
10 | jobs:
11 | SwiftLint:
12 | runs-on: ubuntu-latest
13 | timeout-minutes: 10
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: GitHub Action for SwiftLint (Only files changed in the PR)
18 | uses: norio-nomura/action-swiftlint@3.2.1
19 | with:
20 | args: --strict
21 | env:
22 | SWIFTLINT: true
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | **/fastlane/report.xml
66 | **/fastlane/Preview.html
67 | **/fastlane/screenshots/**/*.png
68 | **/fastlane/test_output
69 | **/GithubIgnore
70 |
71 | # Secrets
72 | secrets.*.xcconfig
73 | !secrets.template.xcconfig
74 |
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Daisuke Adachi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NativeAppTemplate.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/NativeAppTemplate.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/NativeAppTemplate.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "6b1231380720b7bc51910b56c46999018c0dc9e0af9279ee365eff309cd750a5",
3 | "pins" : [
4 | {
5 | "identity" : "keychainaccess",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
8 | "state" : {
9 | "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
10 | "version" : "4.2.2"
11 | }
12 | },
13 | {
14 | "identity" : "swift-collections",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/apple/swift-collections.git",
17 | "state" : {
18 | "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
19 | "version" : "1.1.4"
20 | }
21 | },
22 | {
23 | "identity" : "swiftyjson",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/SwiftyJSON/SwiftyJSON",
26 | "state" : {
27 | "revision" : "af76cf3ef710b6ca5f8c05f3a31307d44a3c5828",
28 | "version" : "5.0.2"
29 | }
30 | }
31 | ],
32 | "version" : 3
33 | }
34 |
--------------------------------------------------------------------------------
/NativeAppTemplate/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | opt_in_rules:
2 | - array_init
3 | - closure_body_length
4 | - closure_spacing
5 | - closure_end_indentation
6 | - collection_alignment
7 | - conditional_returns_on_newline
8 | - contains_over_first_not_nil
9 | - convenience_type
10 | - empty_count
11 | - empty_xctest_method
12 | - explicit_init
13 | - extension_access_modifier
14 | - empty_string
15 | - fallthrough
16 | - fatal_error_message
17 | - first_where
18 | - implicit_return
19 | - joined_default_parameter
20 | - last_where
21 | - legacy_multiple
22 | - legacy_random
23 | - let_var_whitespace
24 | - literal_expression_end_indentation
25 | - lower_acl_than_parent
26 | - modifier_order
27 | - multiline_arguments
28 | - multiline_function_chains
29 | - multiline_parameters
30 | - nimble_operator
31 | - operator_usage_whitespace
32 | - overridden_super_call
33 | - private_action
34 | - prohibited_super_call
35 | - quick_discouraged_call
36 | - quick_discouraged_focused_test
37 | - quick_discouraged_pending_test
38 | - reduce_into
39 | - redundant_nil_coalescing
40 | - redundant_type_annotation
41 | - required_enum_case
42 | - single_test_class
43 | - sorted_first_last
44 | - strong_iboutlet
45 | - switch_case_on_newline
46 | - toggle_bool
47 | - unneeded_parentheses_in_closure_argument
48 | - untyped_error_in_catch
49 | - vertical_parameter_alignment_on_call
50 | - vertical_whitespace_closing_braces
51 | - xct_specific_matcher
52 | - yoda_condition
53 |
54 | disabled_rules: # rule identifiers to exclude from running
55 | - closure_parameter_position
56 | - force_cast
57 | - line_length
58 | - multiple_closures_with_trailing_closure
59 | - todo
60 | - trailing_whitespace
61 | - xctfail_message
62 |
63 | analyzer_rules: # only run with the analyze command
64 | - explicit_self
65 | - unused_import
66 |
67 | excluded: # paths to ignore during linting. overridden by `included`
68 | - Carthage
69 | - Pods
70 |
71 | closure_body_length:
72 | - 100 # warning
73 | - 200 # error
74 |
75 | cyclomatic_complexity:
76 | - 20 # warning
77 | - 25 # error
78 |
79 | large_tuple:
80 | - 3 # warning
81 | - 4 # error
82 |
83 | file_length:
84 | - 1200 # warning
85 | - 1500 # error
86 |
87 | function_body_length:
88 | - 100 # warning
89 | - 300 # error
90 |
91 | type_body_length:
92 | - 1000 # warning
93 | - 1500 # error
94 |
95 | type_name:
96 | allowed_symbols:
97 | - _
98 |
99 | identifier_name:
100 | min_length: 3
101 | max_length: 75
102 | excluded:
103 | - by
104 | - id
105 | - db
106 |
107 | conditional_returns_on_newline:
108 | if_only: true
109 |
--------------------------------------------------------------------------------
/NativeAppTemplate/App.swift:
--------------------------------------------------------------------------------
1 | //
2 | // App.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2024/10/01.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import TipKit
11 |
12 | @main
13 | struct App {
14 | typealias Objects = ( // swiftlint:disable:this large_tuple
15 | loginRepository: LoginRepository,
16 | sessionController: SessionController,
17 | dataManager: DataManager,
18 | messageBus: MessageBus
19 | )
20 |
21 | private var loginRepository: LoginRepository
22 | private var sessionController: SessionController
23 | private var dataManager: DataManager
24 | private var messageBus: MessageBus
25 |
26 | @MainActor init() {
27 | // setup objects
28 | let nativeAppTemplateObjects = App.objects
29 | loginRepository = nativeAppTemplateObjects.loginRepository
30 | sessionController = nativeAppTemplateObjects.sessionController
31 | dataManager = nativeAppTemplateObjects.dataManager
32 | messageBus = nativeAppTemplateObjects.messageBus
33 |
34 | // Tips.showAllTipsForTesting()
35 |
36 | try? Tips.configure()
37 | }
38 | }
39 |
40 | // MARK: - SwiftUI.App
41 | extension App: SwiftUI.App {
42 | var body: some Scene {
43 | WindowGroup {
44 | ZStack {
45 | Rectangle()
46 | .fill(Color.backgroundColor)
47 | .edgesIgnoringSafeArea(.all)
48 | MainView()
49 | .preferredColorScheme(.dark) // Dark mode only
50 | .environment(loginRepository)
51 | .environment(sessionController)
52 | .environment(dataManager)
53 | .environment(messageBus)
54 | }
55 | }
56 | }
57 | }
58 |
59 | // MARK: - internal
60 | extension App {
61 | // Initialise the database
62 | @MainActor static var objects: Objects {
63 | let loginRepository = LoginRepository()
64 | let sessionController = SessionController(loginRepository: loginRepository)
65 | let messageBus = MessageBus()
66 |
67 | return (
68 | loginRepository: loginRepository,
69 | sessionController: sessionController,
70 | dataManager: .init(
71 | sessionController: sessionController
72 | ),
73 | messageBus: messageBus
74 | )
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/NativeAppTemplate/AppSingletons.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @MainActor
4 | struct AppSingletons {
5 | var nfcManager: NFCManager
6 |
7 | init(nfcManager: NFCManager? = nil) {
8 | self.nfcManager = nfcManager ?? NFCManager.shared
9 | }
10 | }
11 |
12 | @MainActor var appSingletons = AppSingletons()
13 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_free.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | },
9 | {
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ],
16 | "filename" : "icon_free 1.png",
17 | "idiom" : "universal",
18 | "platform" : "ios",
19 | "size" : "1024x1024"
20 | },
21 | {
22 | "appearances" : [
23 | {
24 | "appearance" : "luminosity",
25 | "value" : "tinted"
26 | }
27 | ],
28 | "filename" : "icon_free 2.png",
29 | "idiom" : "universal",
30 | "platform" : "ios",
31 | "size" : "1024x1024"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/AppIcon.appiconset/icon_free 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/AppIcon.appiconset/icon_free 1.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/AppIcon.appiconset/icon_free 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/AppIcon.appiconset/icon_free 2.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/AppIcon.appiconset/icon_free.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/AppIcon.appiconset/icon_free.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/backgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x1E",
9 | "green" : "0x16",
10 | "red" : "0x14"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/cardBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x1E",
9 | "green" : "0x16",
10 | "red" : "0x14"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/coloredPrimaryBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xF8",
10 | "red" : "0xE3"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/coloredPrimaryForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x88",
9 | "green" : "0x53",
10 | "red" : "0x03"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/coloredSecondaryBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFA",
9 | "green" : "0xF7",
10 | "red" : "0xF5"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/coloredSecondaryForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x33",
9 | "green" : "0x29",
10 | "red" : "0x1F"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/failureBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x42",
9 | "green" : "0x00",
10 | "red" : "0x62"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/successBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x40",
9 | "green" : "0x4D",
10 | "red" : "0x01"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Backgrounds/successSecondaryForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xE2",
9 | "green" : "0xF7",
10 | "red" : "0xC6"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Button/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Button/coloredPrimaryButtonForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x88",
9 | "green" : "0x53",
10 | "red" : "0x03"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Button/coloredSecondaryButtonForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x33",
9 | "green" : "0x29",
10 | "red" : "0x1F"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Button/destructiveButtonForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFA",
9 | "green" : "0xF7",
10 | "red" : "0xF5"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Button/failureSecondaryForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xD2",
9 | "green" : "0xB8",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Button/primaryButtonForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFA",
9 | "green" : "0xF7",
10 | "red" : "0xF5"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Button/secondaryButtonForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFA",
9 | "green" : "0xF7",
10 | "red" : "0xF5"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Snackbar/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Snackbar/error.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x1E",
9 | "green" : "0x16",
10 | "red" : "0x14"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Snackbar/snackText.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "1.000",
13 | "alpha" : "1.000",
14 | "blue" : "1.000",
15 | "green" : "1.000"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Snackbar/success.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x1E",
9 | "green" : "0x16",
10 | "red" : "0x14"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Snackbar/warning.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x00",
9 | "green" : "0x00",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/completedTagBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xE2",
9 | "green" : "0xF7",
10 | "red" : "0xC6"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/completedTagBorder.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/completedTagForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x40",
9 | "green" : "0x4D",
10 | "red" : "0x01"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/customerScannedTagBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xD2",
9 | "green" : "0xB8",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/customerScannedTagBorder.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/customerScannedTagForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x42",
9 | "green" : "0x00",
10 | "red" : "0x62"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/idlingTagBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEB",
9 | "green" : "0xE7",
10 | "red" : "0xE4"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/idlingTagBorder.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Tags/idlingTagForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x33",
9 | "green" : "0x29",
10 | "red" : "0x1F"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Text/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Text/coloredPrimaryFootnoteText.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xA3",
9 | "green" : "0x69",
10 | "red" : "0x0B"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Text/coloredSecondaryFootnoteText.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x4B",
9 | "green" : "0x3F",
10 | "red" : "0x32"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Text/contentText.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.529",
9 | "green" : "0.463",
10 | "red" : "0.431"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "light"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.525",
27 | "green" : "0.463",
28 | "red" : "0.427"
29 | }
30 | },
31 | "idiom" : "universal"
32 | },
33 | {
34 | "appearances" : [
35 | {
36 | "appearance" : "luminosity",
37 | "value" : "dark"
38 | }
39 | ],
40 | "color" : {
41 | "color-space" : "srgb",
42 | "components" : {
43 | "alpha" : "1.000",
44 | "blue" : "0xB1",
45 | "green" : "0xA5",
46 | "red" : "0x9A"
47 | }
48 | },
49 | "idiom" : "universal"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Text/secondaryText.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x7C",
9 | "green" : "0x6E",
10 | "red" : "0x61"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "light"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x7C",
27 | "green" : "0x6E",
28 | "red" : "0x61"
29 | }
30 | },
31 | "idiom" : "universal"
32 | },
33 | {
34 | "appearances" : [
35 | {
36 | "appearance" : "luminosity",
37 | "value" : "dark"
38 | }
39 | ],
40 | "color" : {
41 | "color-space" : "srgb",
42 | "components" : {
43 | "alpha" : "1.000",
44 | "blue" : "0x7C",
45 | "green" : "0x6E",
46 | "red" : "0x61"
47 | }
48 | },
49 | "idiom" : "universal"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Text/titleText.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFF",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Write to Tag/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Write to Tag/customerBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xF8",
10 | "red" : "0xE3"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Write to Tag/customerForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x88",
9 | "green" : "0x53",
10 | "red" : "0x03"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Write to Tag/lockBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEA",
9 | "green" : "0xFB",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Write to Tag/lockForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x0B",
9 | "green" : "0x2B",
10 | "red" : "0x8D"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Write to Tag/serverBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEC",
9 | "green" : "0xE3",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/Write to Tag/serverForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x42",
9 | "green" : "0x00",
10 | "red" : "0x62"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/accent.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFA",
9 | "green" : "0xD0",
10 | "red" : "0x5E"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/alarm.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x4E",
9 | "green" : "0x4E",
10 | "red" : "0xEF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Colours/lightestAccent.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xF8",
10 | "red" : "0xE3"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Logo/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Logo/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo_white.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview1~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview1~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview1~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1.imageset/overview1~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1.imageset/overview1~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1.imageset/overview1~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1.imageset/overview1~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1.imageset/overview1~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1.imageset/overview1~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding10.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview9~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview9~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview9~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding10.imageset/overview9~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding10.imageset/overview9~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding10.imageset/overview9~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding10.imageset/overview9~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding10.imageset/overview9~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding10.imageset/overview9~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding11.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview9_phone_customer1~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview9_phone_customer1~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview9_phone_customer1~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding11.imageset/overview9_phone_customer1~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding11.imageset/overview9_phone_customer1~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding11.imageset/overview9_phone_customer1~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding11.imageset/overview9_phone_customer1~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding11.imageset/overview9_phone_customer1~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding11.imageset/overview9_phone_customer1~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding12.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview13~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview13~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview13~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding12.imageset/overview13~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding12.imageset/overview13~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding12.imageset/overview13~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding12.imageset/overview13~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding12.imageset/overview13~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding12.imageset/overview13~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding13.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview14~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview14~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview14~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding13.imageset/overview14~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding13.imageset/overview14~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding13.imageset/overview14~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding13.imageset/overview14~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding13.imageset/overview14~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding13.imageset/overview14~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1Slim.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview1_slim~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview1_slim~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview1_slim~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1Slim.imageset/overview1_slim~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1Slim.imageset/overview1_slim~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1Slim.imageset/overview1_slim~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1Slim.imageset/overview1_slim~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1Slim.imageset/overview1_slim~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding1Slim.imageset/overview1_slim~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview2~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview2~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview2~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding2.imageset/overview2~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding2.imageset/overview2~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding2.imageset/overview2~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding2.imageset/overview2~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding2.imageset/overview2~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding2.imageset/overview2~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview6~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview6~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview6~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding3.imageset/overview6~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding3.imageset/overview6~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding3.imageset/overview6~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding3.imageset/overview6~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding3.imageset/overview6~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding3.imageset/overview6~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview6_phone_customer2~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview6_phone_customer2~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview6_phone_customer2~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding4.imageset/overview6_phone_customer2~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding4.imageset/overview6_phone_customer2~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding4.imageset/overview6_phone_customer2~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding4.imageset/overview6_phone_customer2~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding4.imageset/overview6_phone_customer2~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding4.imageset/overview6_phone_customer2~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding5.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview7~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview7~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview7~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding5.imageset/overview7~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding5.imageset/overview7~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding5.imageset/overview7~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding5.imageset/overview7~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding5.imageset/overview7~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding5.imageset/overview7~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview8~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview8~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview8~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding6.imageset/overview8~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding6.imageset/overview8~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding6.imageset/overview8~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding6.imageset/overview8~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding6.imageset/overview8~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding6.imageset/overview8~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding7.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview8_phone_server2~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview8_phone_server2~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview8_phone_server2~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding7.imageset/overview8_phone_server2~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding7.imageset/overview8_phone_server2~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding7.imageset/overview8_phone_server2~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding7.imageset/overview8_phone_server2~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding7.imageset/overview8_phone_server2~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding7.imageset/overview8_phone_server2~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding8.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview8_phone_server3~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview8_phone_server3~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview8_phone_server3~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding8.imageset/overview8_phone_server3~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding8.imageset/overview8_phone_server3~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding8.imageset/overview8_phone_server3~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding8.imageset/overview8_phone_server3~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding8.imageset/overview8_phone_server3~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding8.imageset/overview8_phone_server3~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding9.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "overview8_2~universal@1x.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "overview8_2~universal@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "overview8_2~universal@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding9.imageset/overview8_2~universal@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding9.imageset/overview8_2~universal@1x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding9.imageset/overview8_2~universal@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding9.imageset/overview8_2~universal@2x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding9.imageset/overview8_2~universal@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Assets.xcassets/Onboarding/onboarding9.imageset/overview8_2~universal@3x.png
--------------------------------------------------------------------------------
/NativeAppTemplate/Data/DataManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataManager.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @MainActor @Observable class DataManager {
11 |
12 | // MARK: - Properties
13 | // Initialiser Arguments
14 | var sessionController: SessionController
15 |
16 | // Repositories
17 | private(set) var accountPasswordRepository: AccountPasswordRepository!
18 | private(set) var shopRepository: ShopRepository!
19 | private(set) var itemTagRepository: ItemTagRepository!
20 | private(set) var isRebuildingRepositories = false
21 |
22 | // MARK: - Initializers
23 | init(sessionController: SessionController) {
24 | self.sessionController = sessionController
25 | rebuildRepositories()
26 | }
27 |
28 | func rebuildRepositories() {
29 | isRebuildingRepositories = true
30 |
31 | withObservationTracking {
32 | _ = sessionController.client
33 | } onChange: {
34 | Task { @MainActor in
35 | self.rebuildRepositories()
36 | }
37 | }
38 |
39 | let accountPasswordService = AccountPasswordService(networkClient: sessionController.client)
40 | let shopsService = ShopsService(networkClient: sessionController.client)
41 | let itemTagsService = ItemTagsService(networkClient: sessionController.client)
42 |
43 | accountPasswordRepository = AccountPasswordRepository(accountPasswordService: accountPasswordService)
44 | shopRepository = ShopRepository(shopsService: shopsService)
45 | itemTagRepository = ItemTagRepository(itemTagsService: itemTagsService)
46 |
47 | isRebuildingRepositories = false
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Data/DataState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataState.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/25.
6 | //
7 |
8 | enum DataState {
9 | case initial
10 | case loading
11 | case hasData
12 | case failed
13 | }
14 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Data/Repositories/AccountPasswordRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountPasswordRepository.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/25.
6 | //
7 |
8 | @MainActor class AccountPasswordRepository {
9 | let accountPasswordService: AccountPasswordService
10 |
11 | init(
12 | accountPasswordService: AccountPasswordService
13 | ) {
14 | self.accountPasswordService = accountPasswordService
15 | }
16 |
17 | func update(updatePassword: UpdatePassword) async throws {
18 | do {
19 | try await accountPasswordService.updatePassword(updatePassword: updatePassword)
20 | } catch {
21 | Failure
22 | .destroy(from: Self.self, reason: error.localizedDescription)
23 | .log()
24 | throw error
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Data/Repositories/ShopRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopRepository.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2022/06/28.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @MainActor @Observable class ShopRepository {
11 | let shopsService: ShopsService
12 |
13 | var shops: [Shop] = []
14 | private(set) var state: DataState = .initial
15 | private(set) var limitCount = 0
16 | private(set) var createdShopsCount = 0
17 |
18 | init(
19 | shopsService: ShopsService
20 | ) {
21 | self.shopsService = shopsService
22 | }
23 |
24 | var isEmpty: Bool { shops.isEmpty }
25 |
26 | func findBy(id: String) -> Shop {
27 | let shop = shops.first { $0.id == id }
28 | return shop!
29 | }
30 |
31 | func reload() {
32 | if Task.isCancelled {
33 | return
34 | }
35 |
36 | if state == .loading {
37 | return
38 | }
39 |
40 | state = .loading
41 |
42 | Task { @MainActor in
43 | do {
44 | (shops, limitCount, createdShopsCount) = try await shopsService.allShops()
45 | state = .hasData
46 | } catch {
47 | state = .failed
48 | Failure
49 | .fetch(from: Self.self, reason: error.localizedDescription)
50 | .log()
51 | }
52 | }
53 | }
54 |
55 | func fetchDetail(id: String) async throws -> Shop {
56 | do {
57 | let shop = try await shopsService.shopDetail(id: id)
58 | let shopIndex = (shops.firstIndex { $0.id == shop.id })
59 | if shopIndex != nil {
60 | shops[shopIndex!] = shop
61 | }
62 |
63 | return shop
64 | } catch {
65 | Failure
66 | .fetch(from: Self.self, reason: error.localizedDescription)
67 | .log()
68 | throw error
69 | }
70 | }
71 |
72 | func create(shop: Shop) async throws -> Shop {
73 | do {
74 | let createdShop = try await shopsService.makeShop(shop: shop)
75 | return createdShop
76 | } catch {
77 | Failure
78 | .create(from: Self.self, reason: error.localizedDescription)
79 | .log()
80 | throw error
81 | }
82 | }
83 |
84 | func update(id: String, shop: Shop) async throws -> Shop {
85 | do {
86 | let updatedShop = try await shopsService.updateShop(id: id, shop: shop)
87 | let shopIndex = (shops.firstIndex { $0.id == updatedShop.id })
88 | if shopIndex != nil {
89 | shops[shopIndex!] = updatedShop
90 | }
91 |
92 | return updatedShop
93 | } catch {
94 | Failure
95 | .update(from: Self.self, reason: error.localizedDescription)
96 | .log()
97 | throw error
98 | }
99 | }
100 |
101 | func destroy(id: String) async throws {
102 | do {
103 | try await shopsService.destroyShop(id: id)
104 | } catch {
105 | Failure
106 | .destroy(from: Self.self, reason: error.localizedDescription)
107 | .log()
108 | throw error
109 | }
110 | }
111 |
112 | func reset(id: String) async throws {
113 | do {
114 | try await shopsService.resetShop(id: id)
115 | } catch {
116 | Failure
117 | .destroy(from: Self.self, reason: error.localizedDescription)
118 | .log()
119 | throw error
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Data/ViewModels/TabViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabViewModel.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @Observable class TabViewModel {
11 | var selectedTab: MainTab = .shops
12 |
13 | var showingDetailView = Dictionary(
14 | uniqueKeysWithValues: zip(MainTab.allCases, AnyIterator { false })
15 | )
16 | }
17 |
18 | extension MainTab: EnvironmentKey {
19 | static var defaultValue: Self { .shops }
20 | }
21 |
22 | extension EnvironmentValues {
23 | var mainTab: MainTab {
24 | get { self[MainTab.self] }
25 | set { self[MainTab.self] = newValue }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Extensions/Bundle+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Extensions.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/11/13.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Bundle {
11 | public var appName: String { getInfo("CFBundleName") }
12 | public var displayName: String { getInfo("CFBundleDisplayName") }
13 | public var language: String { getInfo("CFBundleDevelopmentRegion") }
14 | public var identifier: String { getInfo("CFBundleIdentifier") }
15 | public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") }
16 |
17 | public var appBuild: String { getInfo("CFBundleVersion") }
18 | public var appVersionLong: String { getInfo("CFBundleShortVersionString") }
19 |
20 | private func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" }
21 | }
22 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Extensions/Date+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+Extensions.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/11/13.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Date {
11 | func dateByAddingNumberOfSeconds(_ seconds: Int) -> Date {
12 | let timeInterval = TimeInterval(seconds)
13 | return addingTimeInterval(timeInterval)
14 | }
15 |
16 | var cardDateString: String {
17 | let formatter = DateFormatter.cardDateFormatter
18 | return formatter.string(from: self)
19 | }
20 |
21 | var cardTimeString: String {
22 | let formatter = DateFormatter.cardTimeFormatter
23 | return formatter.string(from: self)
24 | }
25 |
26 | var cardTimeAgoInWordsDateString: String {
27 | let formatter = DateFormatter.timeAgoInWordsDateFormatter
28 | return formatter.string(from: self)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Extensions/DateFormatter+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateFormatter+Extensions.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/11/13.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | static let cardDateString: String = "MMM dd yyyy"
12 | static let cardTimeString: String = "HH:mm"
13 | }
14 |
15 | extension ISO8601DateFormatter {
16 | convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!) {
17 | self.init()
18 | self.formatOptions = formatOptions
19 | self.timeZone = timeZone
20 | }
21 | }
22 | extension Formatter {
23 | nonisolated(unsafe) static let iso8601 = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
24 | static let isoDateUtc = {
25 | let dateFormatter = DateFormatter.formatter(for: "yyyy-MM-dd")
26 | dateFormatter.timeZone = NSTimeZone(name: "UTC")! as TimeZone
27 | return dateFormatter
28 | }()
29 | }
30 |
31 | extension String {
32 | var iso8601: Date? {
33 | Formatter.iso8601.date(from: self)
34 | }
35 | }
36 |
37 | extension DateFormatter {
38 | static let cardDateFormatter: DateFormatter = {
39 | DateFormatter.formatter(for: .cardDateString)
40 | }()
41 |
42 | static let cardTimeFormatter: DateFormatter = {
43 | DateFormatter.formatter(for: .cardTimeString)
44 | }()
45 |
46 | static let timeAgoInWordsDateFormatter: DateFormatter = {
47 | let dateFormatter = DateFormatter()
48 | dateFormatter.dateStyle = .short
49 | dateFormatter.timeStyle = .medium
50 | dateFormatter.doesRelativeDateFormatting = true
51 | return dateFormatter
52 | }()
53 |
54 | static func formatter(for dateString: String) -> DateFormatter {
55 | let dateFormatter = DateFormatter()
56 | dateFormatter.dateFormat = dateString
57 | return dateFormatter
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Extensions/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extensions.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2024/01/04.
6 | //
7 |
8 | import UIKit
9 |
10 | extension String {
11 | /// Generates a `UIImage` instance from this string using a specified
12 | /// attributes and size.
13 | ///
14 | /// - Parameters:
15 | /// - attributes: to draw this string with. Default is `nil`.
16 | /// - size: of the image to return.
17 | /// - Returns: a `UIImage` instance from this string using a specified
18 | /// attributes and size, or `nil` if the operation fails.
19 | func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? {
20 | let size = size ?? (self as NSString).size(withAttributes: attributes)
21 | return UIGraphicsImageRenderer(size: size).image { _ in
22 | (self as NSString).draw(in: CGRect(origin: .zero, size: size),
23 | withAttributes: attributes)
24 | }
25 | }
26 |
27 | func isAlphanumeric() -> Bool {
28 | self.rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil && !self.isEmpty
29 | }
30 |
31 | func isAlphanumeric(ignoreDiacritics: Bool = false) -> Bool {
32 | if ignoreDiacritics {
33 | return self.range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil && !self.isEmpty
34 | } else {
35 | return self.isAlphanumeric()
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Extensions/UIApplication+DismissKeyboard.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import UIKit
30 |
31 | extension UIApplication {
32 | static func dismissKeyboard() {
33 | shared.dismissKeyboard()
34 | }
35 |
36 | private func dismissKeyboard() {
37 | sendAction(
38 | #selector(UIResponder.resignFirstResponder),
39 | to: nil,
40 | from: nil,
41 | for: nil)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Extensions/UIImage+Extentions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+Extentions.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UIImage {
11 | func composited(withSmallCenterImage centerImage: UIImage) -> UIImage {
12 | UIGraphicsImageRenderer(size: self.size).image { context in
13 | let imageWidth = context.format.bounds.width
14 | let imageHeight = context.format.bounds.height
15 | let centerImageLength = imageWidth < imageHeight ? imageWidth / 5 : imageHeight / 5
16 | let centerImageRadius = centerImageLength * 0.2
17 |
18 | draw(in: CGRect(origin: CGPoint(x: 0, y: 0),
19 | size: context.format.bounds.size))
20 |
21 | let centerImageRect = CGRect(x: (imageWidth - centerImageLength) / 2,
22 | y: (imageHeight - centerImageLength) / 2,
23 | width: centerImageLength,
24 | height: centerImageLength)
25 |
26 | let roundedRectPath = UIBezierPath(roundedRect: centerImageRect,
27 | cornerRadius: centerImageRadius)
28 | roundedRectPath.addClip()
29 |
30 | centerImage.draw(in: centerImageRect)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Extensions/View+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Extensions.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2024/01/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension View {
11 | var inAllColorSchemes: some View {
12 | ForEach(
13 | ColorScheme.allCases,
14 | id: \.self,
15 | content: preferredColorScheme
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.nfc.readersession.iso7816.select-identifiers
6 |
7 | D2760000850101
8 |
9 | com.apple.developer.nfc.readersession.felica.systemcodes
10 |
11 | 12FC
12 |
13 | NSPhotoLibraryAddUsageDescription
14 | Save a QR code image including web page link used by this app.
15 | NFCReaderUsageDescription
16 | This app uses a NFC to write the application info to the NFC number tag or to read the NFC number tag into the application.
17 | CFBundleDevelopmentRegion
18 | $(DEVELOPMENT_LANGUAGE)
19 | CFBundleDisplayName
20 | NativeAppTemplate Free
21 | CFBundleExecutable
22 | $(EXECUTABLE_NAME)
23 | CFBundleIdentifier
24 | $(PRODUCT_BUNDLE_IDENTIFIER)
25 | CFBundleInfoDictionaryVersion
26 | 6.0
27 | CFBundleName
28 | $(PRODUCT_NAME)
29 | CFBundlePackageType
30 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
31 | CFBundleShortVersionString
32 | $(MARKETING_VERSION)
33 | CFBundleVersion
34 | $(CURRENT_PROJECT_VERSION)
35 | ITSAppUsesNonExemptEncryption
36 |
37 | LSRequiresIPhoneOS
38 |
39 | UIAppFonts
40 |
41 | Inter-Medium.ttf
42 | Inter-Bold.ttf
43 |
44 | UIApplicationSceneManifest
45 |
46 | UIApplicationSupportsMultipleScenes
47 |
48 |
49 | UIApplicationSupportsIndirectInputEvents
50 |
51 | UILaunchScreen
52 |
53 | UIColorName
54 | backgroundColor
55 |
56 | UIRequiredDeviceCapabilities
57 |
58 | arm64
59 |
60 | UIRequiresFullScreen
61 |
62 | UISupportedInterfaceOrientations
63 |
64 | UIInterfaceOrientationPortrait
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Logging/Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2024/01/04.
6 | //
7 |
8 | struct Failure {
9 | static func signUp(from source: Source.Type, reason: String) -> Self {
10 | .init(source: source, action: "signUp", reason: reason)
11 | }
12 |
13 | static func login(from source: Source.Type, reason: String) -> Self {
14 | .init(source: source, action: "login", reason: reason)
15 | }
16 |
17 | static func logout(from source: Source.Type, reason: String) -> Self {
18 | .init(source: source, action: "logout", reason: reason)
19 | }
20 |
21 | static func fetch(from source: Source.Type, reason: String) -> Self {
22 | .init(source: source, action: "fetch", reason: reason)
23 | }
24 |
25 | static func create(from source: Source.Type, reason: String) -> Self {
26 | .init(source: source, action: "create", reason: reason)
27 | }
28 |
29 | static func update(from source: Source.Type, reason: String) -> Self {
30 | .init(source: source, action: "update", reason: reason)
31 | }
32 |
33 | static func destroy(from source: Source.Type, reason: String) -> Self {
34 | .init(source: source, action: "destroy", reason: reason)
35 | }
36 |
37 | private init(
38 | source: Source.Type,
39 | action: String,
40 | reason: String
41 | ) {
42 | self.init(
43 | source: "\(Source.self)",
44 | action: action,
45 | reason: reason
46 | )
47 | }
48 |
49 | private init(
50 | source: String,
51 | action: String,
52 | reason: String
53 | ) {
54 | self.source = source
55 | self.action = "Failed_\(action)"
56 | self.reason = reason
57 | }
58 |
59 | private let source: String
60 | private let action: String
61 | private let reason: String
62 |
63 | func log() {
64 | print(
65 | [ "source": source,
66 | "action": action,
67 | "reason": reason
68 | ]
69 | )
70 | }
71 | }
72 |
73 | struct Event {
74 | static func login(from source: Source.Type) -> Self {
75 | .init(
76 | source: "\(Source.self)",
77 | action: "Login"
78 | )
79 | }
80 |
81 | static func refresh(
82 | from source: Source.Type,
83 | action: String
84 | ) -> Self {
85 | .init(
86 | source: "\(Source.self)",
87 | action: "Refresh"
88 | )
89 | }
90 |
91 | private let source: String
92 | private let action: String
93 |
94 | func log() {
95 | print("EVENT:: \(["source": source, "action": action])")
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Login/LoginRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginRepository.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2021/01/11.
6 | //
7 |
8 | import Foundation
9 |
10 | @MainActor @Observable public class LoginRepository {
11 | // MARK: - Properties
12 | private var _currentShopkeeper: Shopkeeper?
13 |
14 | public var currentShopkeeper: Shopkeeper? {
15 | if _currentShopkeeper == nil {
16 | let keychainStore = LoggedInShopkeeperKeychainStore()
17 |
18 | do {
19 | let loggedInShopkeeper = try keychainStore.retrieve()
20 | _currentShopkeeper = Shopkeeper(from: loggedInShopkeeper)
21 | } catch {
22 | print(error)
23 | }
24 | }
25 | return _currentShopkeeper
26 | }
27 |
28 | @MainActor func login(email: String, password: String) async throws -> Shopkeeper {
29 | do {
30 | let sessionsService = SessionsService(networkClient: NativeAppTemplateAPI(authToken: "", client: "", expiry: "", uid: "", accountId: ""))
31 | let shopkeeper = try await sessionsService.makeSession(email: email, password: password)
32 | try saveShopkeeper(shopkeeper: shopkeeper)
33 | _currentShopkeeper = shopkeeper
34 | } catch {
35 | Failure
36 | .fetch(from: Self.self, reason: error.localizedDescription)
37 | .log()
38 | throw error
39 | }
40 | return currentShopkeeper!
41 | }
42 |
43 | @MainActor func logout(networkClient: NativeAppTemplateAPI) async throws {
44 | do {
45 | let sessionsService = SessionsService(networkClient: networkClient)
46 | try await sessionsService.destroySession()
47 | removeShopkeeper()
48 | _currentShopkeeper = .none
49 | } catch {
50 | Failure
51 | .fetch(from: Self.self, reason: error.localizedDescription)
52 | .log()
53 | removeShopkeeper()
54 | _currentShopkeeper = .none
55 | throw error
56 | }
57 | }
58 |
59 | public func updateShopkeeper(shopkeeper: Shopkeeper?) throws {
60 | _currentShopkeeper = shopkeeper
61 | if let shopkeeper = shopkeeper {
62 | try saveShopkeeper(shopkeeper: shopkeeper)
63 | } else {
64 | removeShopkeeper()
65 | }
66 | }
67 |
68 | private func saveShopkeeper(shopkeeper: Shopkeeper) throws {
69 | let keychainStore = LoggedInShopkeeperKeychainStore()
70 | let loggedInShopkeeper = LoggedInShopkeeper(from: shopkeeper)
71 | do {
72 | try keychainStore.store(loggedInShopkeeper)
73 | } catch {
74 | throw error
75 | }
76 | }
77 |
78 | private func removeShopkeeper() {
79 | let keychainStore = LoggedInShopkeeperKeychainStore()
80 |
81 | do {
82 | try keychainStore.remove()
83 | } catch {
84 | print(error)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Login/OnboardingRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnboardingRepository.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import Foundation
9 | import OrderedCollections
10 |
11 | @MainActor @Observable class OnboardingRepository {
12 | var onboardings: [Onboarding] = []
13 | let onboardingsDictionary: OrderedDictionary = [
14 | 1: false,
15 | 2: false,
16 | 3: false,
17 | 4: true,
18 | 5: false,
19 | 6: false,
20 | 7: true,
21 | 8: true,
22 | 9: false,
23 | 10: false,
24 | 11: true,
25 | 12: false,
26 | 13: false
27 | ]
28 |
29 | func reload() {
30 | onboardings = onboardingsDictionary.map { key, value in
31 | Onboarding(id: key, isPortraitImage: value)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Login/SessionRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionRequest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2021/01/11.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | struct MakeSessionRequest: Request {
12 | typealias Response = Shopkeeper
13 |
14 | // MARK: - Properties
15 | var method: HTTPMethod { .POST }
16 | var path: String { "/shopkeeper_auth/sign_in" }
17 | var additionalHeaders: [String: String] = [:]
18 | var body: Data? {
19 | let json: [String: Any] = ["email": email, "password": password]
20 | return try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
21 | }
22 |
23 | // MARK: - Parameters
24 | let email: String
25 | let password: String
26 |
27 | func handle(response: Data) throws -> Shopkeeper {
28 | let json = try JSON(data: response)
29 | let doc = JSONAPIDocument(json)
30 | let shopkeepers = try doc.data.map { try ShopkeeperSignInAdapter.process(resource: $0) }
31 | return shopkeepers.first!
32 | }
33 | }
34 |
35 | struct DestroySessionRequest: Request {
36 | typealias Response = Void
37 |
38 | // MARK: - Properties
39 | var method: HTTPMethod { .DELETE }
40 | var path: String { "/shopkeeper_auth/sign_out" }
41 | var additionalHeaders: [String: String] = [:]
42 |
43 | var body: Data? { nil }
44 |
45 | // MARK: - Internal
46 | func handle(response: Data) throws { }
47 | }
48 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Login/SignUpRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignUpRepository.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2022/07/07.
6 | //
7 |
8 | import Foundation
9 |
10 | @MainActor class SignUpRepository {
11 | func signUp(signUp: SignUp) async throws -> Shopkeeper {
12 | var shopkeeper: Shopkeeper
13 |
14 | do {
15 | let signUpsService = SignUpsService(networkClient: NativeAppTemplateAPI(authToken: "", client: "", expiry: "", uid: "", accountId: ""))
16 | shopkeeper = try await signUpsService.makeShopkeeper(signUp: signUp)
17 | } catch {
18 | Failure
19 | .fetch(from: Self.self, reason: error.localizedDescription)
20 | .log()
21 | throw error
22 | }
23 | return shopkeeper
24 | }
25 |
26 | func update(id: String, signUp: SignUp, networkClient: NativeAppTemplateAPI) async throws -> Shopkeeper {
27 | var shopkeeper: Shopkeeper
28 |
29 | do {
30 | let signUpsService = SignUpsService(networkClient: networkClient)
31 | shopkeeper = try await signUpsService.updateShopkeeper(id: id, signUp: signUp)
32 | } catch {
33 | Failure
34 | .update(from: Self.self, reason: error.localizedDescription)
35 | .log()
36 | throw error
37 | }
38 | return shopkeeper
39 | }
40 |
41 | func destroy(networkClient: NativeAppTemplateAPI) async throws {
42 | do {
43 | let signUpsService = SignUpsService(networkClient: networkClient)
44 | try await signUpsService.destroyShopkeeper()
45 | removeShopkeeper()
46 | } catch {
47 | Failure
48 | .fetch(from: Self.self, reason: error.localizedDescription)
49 | .log()
50 | removeShopkeeper()
51 |
52 | throw error
53 | }
54 | }
55 |
56 | func sendResetPasswordInstruction(sendResetPassword: SendResetPassword) async throws {
57 | do {
58 | let signUpsService = SignUpsService(networkClient: NativeAppTemplateAPI(authToken: "", client: "", expiry: "", uid: "", accountId: ""))
59 | try await signUpsService.sendResetPasswordInstruction(sendResetPassword: sendResetPassword)
60 | } catch {
61 | Failure
62 | .fetch(from: Self.self, reason: error.localizedDescription)
63 | .log()
64 |
65 | throw error
66 | }
67 | }
68 |
69 | func sendConfirmationInstruction(sendConfirmation: SendConfirmation) async throws {
70 | do {
71 | let signUpsService = SignUpsService(networkClient: NativeAppTemplateAPI(authToken: "", client: "", expiry: "", uid: "", accountId: ""))
72 | try await signUpsService.sendConfirmationInstruction(sendConfirmation: sendConfirmation)
73 | } catch {
74 | Failure
75 | .fetch(from: Self.self, reason: error.localizedDescription)
76 | .log()
77 | throw error
78 | }
79 | }
80 |
81 | private func removeShopkeeper() {
82 | let keychainStore = LoggedInShopkeeperKeychainStore()
83 |
84 | do {
85 | try keychainStore.remove()
86 | } catch {
87 | print(error)
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/CompleteScanResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CompleteScanResult.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import Foundation
9 |
10 | enum CompleteScanResultType {
11 | case idled
12 | case completed
13 | case reset
14 | case failed
15 |
16 | var displayString: String {
17 | switch self {
18 | case .idled:
19 | return "Idling"
20 | case .completed:
21 | return "Completed!"
22 | case .reset:
23 | return "Reset!"
24 | case .failed:
25 | return "Failed"
26 | }
27 | }
28 | }
29 |
30 | struct CompleteScanResult {
31 | var itemTag: ItemTag?
32 | var type: CompleteScanResultType = .idled
33 | var message = ""
34 | var scannedAt = Date.now
35 | }
36 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ItemTag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTag.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/01.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ItemTag: Codable, Hashable, Identifiable, Sendable {
11 | var id: String = ""
12 | var shopId: String = ""
13 | var queueNumber: String = ""
14 | var state = ItemTagState.idled
15 | var scanState = ScanState.unscanned
16 | var createdAt = Date.now
17 | var customerReadAt: Date?
18 | var completedAt: Date?
19 | var shopName: String = ""
20 | var alreadyCompleted: Bool?
21 | }
22 |
23 | extension ItemTag {
24 | func scanUrl(itemTagType: ItemTagType) -> URL {
25 | Utility.scanUrl(itemTagId: id, itemTagType: itemTagType.toJson())
26 | }
27 |
28 | func toJson() -> [String: Any] {
29 | ["item_tag":
30 | [
31 | "queue_number": queueNumber
32 | ]
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ItemTagData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagData.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ItemTagData: Identifiable {
11 | var id: String {
12 | itemTagId
13 | }
14 | var itemTagId: String
15 | var itemTagType: ItemTagType
16 | var isReadOnly: Bool
17 | var scannedAt: Date
18 | }
19 |
20 | // MARK: - Equatable
21 | extension ItemTagData: Equatable {
22 | static func == (lhs: ItemTagData, rhs: ItemTagData) -> Bool {
23 | lhs.itemTagId == rhs.itemTagId &&
24 | lhs.itemTagType == rhs.itemTagType &&
25 | lhs.isReadOnly == rhs.isReadOnly &&
26 | lhs.scannedAt == rhs.scannedAt
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ItemTagInfoFromNdefMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagInfoFromNdefMessage.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ItemTagInfoFromNdefMessage {
11 | var id: String
12 | var type: String
13 | var success: Bool
14 | var message: String
15 |
16 | init() {
17 | self.id = ""
18 | self.type = ""
19 | self.success = false
20 | self.message = .messageWrittenOnTagIsWrong
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ItemTagState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagState.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/01.
6 | //
7 |
8 | enum ItemTagState: String, CaseIterable, Identifiable, Codable {
9 | case idled,
10 | completed
11 |
12 | var id: Self { self }
13 |
14 | init(string: String) {
15 | switch string {
16 | case "idled":
17 | self = .idled
18 | case "completed":
19 | self = .completed
20 | default:
21 | self = .idled
22 | }
23 | }
24 |
25 | var displayString: String {
26 | switch self {
27 | case .idled:
28 | return "Idling"
29 | case .completed:
30 | return "Completed"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ItemTagType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagType.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/01.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ItemTagType: String, CaseIterable, Identifiable, Codable {
11 | case server
12 | case customer
13 |
14 | var id: Self { self }
15 |
16 | init(string: String) {
17 | switch string {
18 | case "server":
19 | self = .server
20 | case "customer":
21 | self = .customer
22 | default:
23 | self = .server
24 | }
25 | }
26 |
27 | func toJson() -> String {
28 | switch self {
29 | case .server:
30 | return "server"
31 | case .customer:
32 | return "customer"
33 | }
34 | }
35 |
36 | var displayString: String {
37 | switch self {
38 | case .server:
39 | return "Server"
40 | case .customer:
41 | return "Customer"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/MainTab.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainTab.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/11/03.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum MainTab {
12 | case shops
13 | case scan
14 | case settings
15 | }
16 |
17 | // MARK: - CaseIterable
18 | extension MainTab: CaseIterable { }
19 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/Onboarding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Onboarding.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | struct Onboarding: Hashable, Codable, Identifiable {
9 | var id: Int
10 | var isPortraitImage: Bool = false
11 | }
12 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ScanResultError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanResultError.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ScanResultError: Error {
11 | case failed(String)
12 | }
13 |
14 | extension ScanResultError: LocalizedError {
15 | var errorDescription: String? {
16 | switch self {
17 | case .failed(let message):
18 | return message
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ScanState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanState.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/01.
6 | //
7 |
8 | enum ScanState: String, Identifiable, CaseIterable, Codable {
9 | case unscanned,
10 | scanned
11 |
12 | var id: Self { self }
13 |
14 | init(string: String) {
15 | switch string {
16 | case "unscanned":
17 | self = .unscanned
18 | case "scanned":
19 | self = .scanned
20 | default:
21 | self = .unscanned
22 | }
23 | }
24 |
25 | func toJson() -> String {
26 | switch self {
27 | case .unscanned:
28 | return "unscanned"
29 | case .scanned:
30 | return "scanned"
31 | }
32 | }
33 |
34 | var displayString: String {
35 | switch self {
36 | case .unscanned:
37 | return "Unscanned"
38 | case .scanned:
39 | return "Scanned"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ScrollToTopID.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollToTopID.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/11/03.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ScrollToTopID {
11 | let mainTab: MainTab
12 | let detail: Bool
13 | }
14 |
15 | extension ScrollToTopID: Hashable { }
16 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/SendConfirmation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SendConfirmation.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/09/30.
6 | //
7 |
8 | import Foundation
9 |
10 | struct SendConfirmation: Codable {
11 | var email: String
12 | var redirectUrl: String = NativeAppTemplateEnvironment.prod.baseURL.appendingPathComponent("/shopkeeper_auth/confirmation_result").absoluteString
13 | }
14 |
15 | extension SendConfirmation {
16 | func toJson() -> [String: Any] {
17 | [
18 | "email": email,
19 | "redirect_url": redirectUrl
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/SendResetPassword.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SendResetPassword.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/03/03.
6 | //
7 |
8 | import Foundation
9 |
10 | struct SendResetPassword: Codable {
11 | var email: String
12 | var redirectUrl: String = NativeAppTemplateEnvironment.prod.baseURL.appendingPathComponent("/shopkeeper_auth/reset_password/edit").absoluteString
13 | }
14 |
15 | extension SendResetPassword {
16 | func toJson() -> [String: Any] {
17 | [
18 | "email": email,
19 | "redirect_url": redirectUrl
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/Shop.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Shop.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2021/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Shop: Codable, Identifiable, Sendable {
11 | var id: String
12 | var name: String
13 | var description: String
14 | var timeZone: String
15 | var itemTagsCount: Int = 0
16 | var scannedItemTagsCount: Int = 0
17 | var completedItemTagsCount: Int = 0
18 | var displayShopServerPath: String = ""
19 | }
20 |
21 | extension Shop {
22 | var displayShopServerUrl: URL {
23 | URL(string: "\(NativeAppTemplateEnvironment.prod.baseURL.absoluteString)\(displayShopServerPath)")!
24 | }
25 |
26 | func toJsonForCreate() -> [String: Any] {
27 | [
28 | "shop": [
29 | "name": name,
30 | "description": description,
31 | "time_zone": timeZone
32 | ] as [String: Any]
33 | ]
34 | }
35 |
36 | func toJsonForUpdate() -> [String: Any] {
37 | [
38 | "shop": [
39 | "name": name,
40 | "description": description,
41 | "time_zone": timeZone
42 | ] as [String: Any]
43 | ]
44 | }
45 | }
46 |
47 | extension Shop: Hashable {
48 | static func == (lhs: Shop, rhs: Shop) -> Bool {
49 | lhs.id == rhs.id &&
50 | lhs.name == rhs.name &&
51 | lhs.description == rhs.description &&
52 | lhs.timeZone == rhs.timeZone &&
53 | lhs.itemTagsCount == rhs.itemTagsCount &&
54 | lhs.scannedItemTagsCount == rhs.scannedItemTagsCount &&
55 | lhs.completedItemTagsCount == rhs.completedItemTagsCount &&
56 | lhs.displayShopServerPath == rhs.displayShopServerPath
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/ShowTagInfoScanResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShowTagInfoScanResult.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ShowTagInfoScanResultType {
11 | case idled
12 | case succeeded
13 | case failed
14 | }
15 |
16 | struct ShowTagInfoScanResult {
17 | var itemTag: ItemTag?
18 | var itemTagType: ItemTagType = .server
19 | var isReadOnly = false
20 | var type: ShowTagInfoScanResultType = .idled
21 | var message = ""
22 | var scannedAt = Date.now
23 | }
24 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/SignUp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignUp.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | struct SignUp: Codable {
11 | var name: String
12 | var email: String
13 | var timeZone: String
14 | var currentPlatform: String = "ios"
15 | var password: String?
16 | }
17 |
18 | extension SignUp {
19 | func toJsonForCreate() -> [String: Any] {
20 | [
21 | "name": name,
22 | "email": email,
23 | "time_zone": timeZone,
24 | "current_platform": currentPlatform,
25 | "password": password!
26 | ]
27 | }
28 |
29 | func toJsonForUpdate() -> [String: Any] {
30 | [
31 | "name": name,
32 | "email": email,
33 | "time_zone": timeZone
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Models/UpdatePassword.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdatePassword.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/25.
6 | //
7 |
8 | import Foundation
9 |
10 | struct UpdatePassword: Codable {
11 | var currentPassword: String
12 | var password: String
13 | var passwordConfirmation: String
14 | }
15 |
16 | extension UpdatePassword {
17 | func toJson() -> [String: Any] {
18 | [ "shopkeeper":
19 | [
20 | "current_password": currentPassword,
21 | "password": password,
22 | "password_confirmation": passwordConfirmation
23 | ]
24 | ]
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/NativeAppTemplate/NativeAppTemplate.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.associated-domains
6 |
7 | applinks:api.nativeapptemplate.com
8 |
9 | com.apple.developer.nfc.readersession.formats
10 |
11 | TAG
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Adapters/EntityAdapters/ItemTagAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagAdapter.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/01.
6 | //
7 |
8 | import struct Foundation.URL
9 |
10 | struct ItemTagAdapter: EntityAdapter {
11 | static func process(resource: JSONAPIResource, relationships: [EntityRelationship] = [], cacheUpdate: DataCacheUpdate = DataCacheUpdate()) throws -> ItemTag {
12 | guard resource.entityType == .itemTag else { throw EntityAdapterError.invalidResourceTypeForAdapter }
13 |
14 | guard let shopId = resource.attributes["shop_id"] as? String,
15 | let queueNumber = resource.attributes["queue_number"] as? String,
16 | let state = resource.attributes["state"] as? String,
17 | let scanState = resource.attributes["scan_state"] as? String,
18 | let createdAtString = resource.attributes["created_at"] as? String,
19 | let shopName = resource.attributes["shop_name"] as? String
20 | else {
21 | throw EntityAdapterError.invalidOrMissingAttributes
22 | }
23 |
24 | let createdAt = createdAtString.iso8601!
25 |
26 | let customerReadAtString = resource.attributes["customer_read_at"] as? String
27 | let customerReadAt = customerReadAtString?.iso8601
28 |
29 | let completedAtString = resource.attributes["completed_at"] as? String
30 | let completedAt = completedAtString?.iso8601
31 |
32 | let alreadyCompleted = resource.attributes["already_completed"] as? Bool
33 |
34 | return ItemTag(
35 | id: resource.id,
36 | shopId: shopId,
37 | queueNumber: queueNumber,
38 | state: ItemTagState(string: state),
39 | scanState: ScanState(string: scanState),
40 | createdAt: createdAt,
41 | customerReadAt: customerReadAt,
42 | completedAt: completedAt,
43 | shopName: shopName,
44 | alreadyCompleted: alreadyCompleted
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Adapters/EntityAdapters/ShopAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopAdapter.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2022/06/28.
6 | //
7 |
8 | import struct Foundation.URL
9 |
10 | struct ShopAdapter: EntityAdapter {
11 | static func process(resource: JSONAPIResource, relationships: [EntityRelationship] = [], cacheUpdate: DataCacheUpdate = DataCacheUpdate()) throws -> Shop {
12 | guard resource.entityType == .shop else { throw EntityAdapterError.invalidResourceTypeForAdapter }
13 |
14 | guard let name = resource.attributes["name"] as? String,
15 | let timeZone = resource.attributes["time_zone"] as? String,
16 | let displayShopServerPath = resource.attributes["display_shop_server_path"] as? String
17 | else {
18 | throw EntityAdapterError.invalidOrMissingAttributes
19 | }
20 |
21 | return Shop(
22 | id: resource.id,
23 | name: name,
24 | description: resource.attributes["description"] as? String ?? "",
25 | timeZone: timeZone,
26 | itemTagsCount: resource.attributes["item_tags_count"] as? Int ?? 0,
27 | scannedItemTagsCount: resource.attributes["scanned_item_tags_count"] as? Int ?? 0,
28 | completedItemTagsCount: resource.attributes["completed_item_tags_count"] as? Int ?? 0,
29 | displayShopServerPath: displayShopServerPath
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Adapters/EntityAdapters/ShopkeeperAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopkeeperAdapter.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2021/01/16.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ShopkeeperAdapter: EntityAdapter {
11 | static func process(resource: JSONAPIResource, relationships: [EntityRelationship] = [], cacheUpdate: DataCacheUpdate = DataCacheUpdate()) throws -> Shopkeeper {
12 | guard resource.entityType == .shopkeeper else {
13 | throw EntityAdapterError.invalidResourceTypeForAdapter
14 | }
15 |
16 | guard let email = resource.attributes["email"] as? String,
17 | let name = resource.attributes["name"] as? String,
18 | let timeZone = resource.attributes["time_zone"] as? String
19 | else {
20 | throw EntityAdapterError.invalidOrMissingAttributes
21 | }
22 |
23 | return Shopkeeper(
24 | id: resource.id,
25 | accountId: "",
26 | personalAccountId: "",
27 | accountOwnerId: "",
28 | accountName: "",
29 | email: email,
30 | name: name,
31 | timeZone: timeZone,
32 | uid: "",
33 | token: "",
34 | client: "",
35 | expiry: ""
36 | )!
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Adapters/EntityAdapters/ShopkeeperSignInAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopkeeperSignInAdapter.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2022/08/11.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ShopkeeperSignInAdapter: EntityAdapter {
11 | static func process(resource: JSONAPIResource, relationships: [EntityRelationship] = [], cacheUpdate: DataCacheUpdate = DataCacheUpdate()) throws -> Shopkeeper {
12 | guard resource.entityType == .shopkeeperSignIn else {
13 | throw EntityAdapterError.invalidResourceTypeForAdapter
14 | }
15 |
16 | guard let accountId = resource.attributes["account_id"] as? String,
17 | let personalAccountId = resource.attributes["personal_account_id"] as? String,
18 | let accountOwnerId = resource.attributes["account_owner_id"] as? String,
19 | let accountName = resource.attributes["account_name"] as? String,
20 | let email = resource.attributes["email"] as? String,
21 | let name = resource.attributes["name"] as? String,
22 | let timeZone = resource.attributes["time_zone"] as? String,
23 | let uid = resource.attributes["uid"] as? String
24 | else {
25 | throw EntityAdapterError.invalidOrMissingAttributes
26 | }
27 |
28 | return Shopkeeper(
29 | id: resource.id,
30 | accountId: accountId,
31 | personalAccountId: personalAccountId,
32 | accountOwnerId: accountOwnerId,
33 | accountName: accountName,
34 | email: email,
35 | name: name,
36 | timeZone: timeZone,
37 | uid: uid,
38 | token: resource.attributes["token"] as? String ?? "",
39 | client: resource.attributes["client"] as? String ?? "",
40 | expiry: resource.attributes["expiry"] as? String ?? ""
41 | )!
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/JSONAPI/JSONAPIError.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import struct Foundation.URL
30 | import SwiftyJSON
31 |
32 | public class JSONAPIError {
33 | // MARK: - Properties
34 | var id: String = ""
35 | var links: [String: URL] = [:]
36 | var status: String = ""
37 | var code: String = ""
38 | var title: String = ""
39 | var detail: String = ""
40 | var source: JSONAPIErrorSource?
41 | var meta: [String: Any] = [:]
42 |
43 | // MARK: - Initializers
44 | convenience init(_ json: JSON) {
45 | self.init()
46 |
47 | id = json["id"].stringValue
48 |
49 | if let linksDict = json["links"].dictionaryObject {
50 | for link in linksDict {
51 | if let strValue = link.value as? String,
52 | let url = URL(string: strValue) {
53 | links[link.key] = url
54 | }
55 | }
56 | }
57 |
58 | status = json["status"].stringValue
59 | code = json["code"].stringValue
60 | title = json["title"].stringValue
61 | detail = json["detail"].stringValue
62 | source = JSONAPIErrorSource(json["source"])
63 | meta = json["meta"].dictionaryValue
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/JSONAPI/JSONAPIErrorSource.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import SwiftyJSON
30 |
31 | public class JSONAPIErrorSource {
32 | // MARK: - Properties
33 | var pointer: String = ""
34 | var parameter: String = ""
35 |
36 | // MARK: - Initializers
37 | convenience init(_ json: JSON) {
38 | self.init()
39 |
40 | pointer = json["pointer"].stringValue
41 | parameter = json["parameter"].stringValue
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/JSONAPI/JSONAPIRelationship.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import struct Foundation.URL
30 | import SwiftyJSON
31 |
32 | public class JSONAPIRelationship {
33 | // MARK: - Properties
34 | var meta: [String: Any] = [:]
35 | var data: [JSONAPIResource] = []
36 | var links: [String: URL] = [:]
37 | var type: String = ""
38 |
39 | // MARK: - Initializers
40 | convenience init(_ json: JSON,
41 | type: String,
42 | parent: JSONAPIDocument?) {
43 | self.init()
44 |
45 | self.type = type
46 | meta = json["meta"].dictionaryObject ?? [:]
47 | self.data = json["data"].arrayValue.map {
48 | JSONAPIResource($0, parent: nil)
49 | }
50 |
51 | let nonArrayJSON = json["data"]
52 | let nonArrayJSONAPIResource = JSONAPIResource(nonArrayJSON, parent: nil)
53 | data.append(nonArrayJSONAPIResource)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Network/NativeAppTemplateEnvironment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NativeAppTemplate.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2022/07/03.
6 | //
7 |
8 | import struct Foundation.URL
9 |
10 | struct NativeAppTemplateEnvironment: Equatable {
11 |
12 | // MARK: - Properties
13 | var baseURL: URL
14 | let basePath = "/api/v1"
15 | }
16 |
17 | extension NativeAppTemplateEnvironment {
18 | static let urlString = if String.port.isEmpty {
19 | "\(String.scheme)://\(String.domain)"
20 | } else {
21 | "\(String.scheme)://\(String.domain):\(String.port)"
22 | }
23 |
24 | static let prod = NativeAppTemplateEnvironment(baseURL: URL(string: urlString)!)
25 | }
26 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Requests/AccountPasswordRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountPasswordRequest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/25.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | struct UpdateAccountPasswordRequest: Request {
12 | typealias Response = Void
13 |
14 | // MARK: - Properties
15 | var method: HTTPMethod { .PATCH }
16 | var path: String { "/shopkeeper/account/password" }
17 | var additionalHeaders: [String: String] = [:]
18 | var body: Data? {
19 | let json = updatePassword.toJson()
20 | return try? JSONSerialization.data(withJSONObject: json)
21 | }
22 |
23 | let updatePassword: UpdatePassword
24 |
25 | // MARK: - Internal
26 | func handle(response: Data) throws { }
27 | }
28 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Requests/MeRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeRequest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/12/23.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | struct UpdateConfirmedPrivacyVersionRequest: Request {
12 | typealias Response = Void
13 |
14 | // MARK: - Properties
15 | var method: HTTPMethod { .PATCH }
16 | var path: String { "/shopkeeper/me/update_confirmed_privacy_version" }
17 | var additionalHeaders: [String: String] = [:]
18 | var body: Data? { nil }
19 |
20 | // MARK: - Internal
21 | func handle(response: Data) throws { }
22 | }
23 |
24 | struct UpdateConfirmedTermsVersionRequest: Request {
25 | typealias Response = Void
26 |
27 | // MARK: - Properties
28 | var method: HTTPMethod { .PATCH }
29 | var path: String { "/shopkeeper/me/update_confirmed_terms_version" }
30 | var additionalHeaders: [String: String] = [:]
31 | var body: Data? { nil }
32 |
33 | // MARK: - Internal
34 | func handle(response: Data) throws { }
35 | }
36 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Requests/Parameters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Parameter.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2022/06/28.
6 | //
7 |
8 | struct Parameter: Hashable, Codable {
9 | let key: String
10 | let value: String
11 | }
12 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Requests/Request.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import struct Foundation.Data
30 | import SwiftyJSON
31 |
32 | enum HTTPMethod: String {
33 | case GET
34 | case POST
35 | case PUT
36 | case DELETE
37 | case PATCH
38 | }
39 |
40 | protocol Request {
41 | associatedtype Response
42 |
43 | var method: HTTPMethod { get }
44 | var path: String { get }
45 | var additionalHeaders: [String: String] { get }
46 | var body: Data? { get }
47 |
48 | func handle(response: Data) throws -> Response
49 | }
50 |
51 | // Default implementation to .GET
52 | extension Request {
53 | var method: HTTPMethod { .GET }
54 | var body: Data? { nil }
55 | }
56 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Services/AccountPasswordService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountPasswordService.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/25.
6 | //
7 |
8 | import class Foundation.URLSession
9 |
10 | struct AccountPasswordService: Service {
11 | let networkClient: NativeAppTemplateAPI
12 | let session = URLSession(configuration: .default)
13 | }
14 |
15 | extension AccountPasswordService {
16 | func updatePassword(updatePassword: UpdatePassword) async throws -> UpdateAccountPasswordRequest.Response {
17 | let request = UpdateAccountPasswordRequest(updatePassword: updatePassword)
18 | return try await makeRequest(request: request)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Services/ItemTagsService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagsService.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/01.
6 | //
7 |
8 | import class Foundation.URLSession
9 |
10 | struct ItemTagsService: Service {
11 | let networkClient: NativeAppTemplateAPI
12 | let session = URLSession(configuration: .default)
13 | }
14 |
15 | extension ItemTagsService {
16 | // MARK: - Internal
17 | func allItemTags(shopId: String) async throws -> GetItemTagsRequest.Response {
18 | let request = GetItemTagsRequest(shopId: shopId)
19 | return try await makeRequest(request: request)
20 | }
21 |
22 | func itemTagDetail(id: String) async throws -> GetItemTagDetailRequest.Response {
23 | try await makeRequest(request: GetItemTagDetailRequest(id: id))
24 | }
25 |
26 | func makeItemTag(shopId: String, itemTag: ItemTag) async throws -> MakeItemTagRequest.Response {
27 | let request = MakeItemTagRequest(shopId: shopId, itemTag: itemTag)
28 | return try await makeRequest(request: request)
29 | }
30 |
31 | func updateItemTag(id: String, itemTag: ItemTag) async throws -> UpdateItemTagRequest.Response {
32 | let request = UpdateItemTagRequest(id: id, itemTag: itemTag)
33 | return try await makeRequest(request: request)
34 | }
35 |
36 | func destroyItemTag(id: String) async throws -> DestroyItemTagRequest.Response {
37 | try await makeRequest(request: DestroyItemTagRequest(id: id))
38 | }
39 |
40 | func completeItemTag(id: String) async throws -> CompleteItemTagRequest.Response {
41 | try await makeRequest(request: CompleteItemTagRequest(id: id))
42 | }
43 |
44 | func resetItemTag(id: String) async throws -> ResetItemTagRequest.Response {
45 | try await makeRequest(request: ResetItemTagRequest(id: id))
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Services/MeService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeService.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/12/23.
6 | //
7 |
8 | import class Foundation.URLSession
9 |
10 | struct MeService: Service {
11 | let networkClient: NativeAppTemplateAPI
12 | let session = URLSession(configuration: .default)
13 | }
14 |
15 | // MARK: - Internal
16 | extension MeService {
17 | func updateConfirmedPrivacyVersion() async throws -> UpdateConfirmedPrivacyVersionRequest.Response {
18 | try await makeRequest(request: UpdateConfirmedPrivacyVersionRequest())
19 | }
20 |
21 | func updateConfirmedTermsVersion() async throws -> UpdateConfirmedTermsVersionRequest.Response {
22 | try await makeRequest(request: UpdateConfirmedTermsVersionRequest())
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Services/PermissionsService.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import class Foundation.URLSession
30 |
31 | struct PermissionsService: Service {
32 | let networkClient: NativeAppTemplateAPI
33 | let session = URLSession(configuration: .default)
34 | }
35 |
36 | extension PermissionsService {
37 | func allPermissions() async throws -> PermissionsRequest.Response {
38 | try await makeRequest(request: PermissionsRequest())
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Networking/Services/ShopsService.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import class Foundation.URLSession
30 |
31 | struct ShopsService: Service {
32 | let networkClient: NativeAppTemplateAPI
33 | let session = URLSession(configuration: .default)
34 | }
35 |
36 | // MARK: - Internal
37 | extension ShopsService {
38 | func allShops() async throws -> GetShopsRequest.Response {
39 | try await makeRequest(request: GetShopsRequest())
40 | }
41 |
42 | func updateShop(id: String, shop: Shop) async throws -> UpdateShopRequest.Response {
43 | let request = UpdateShopRequest(id: id, shop: shop)
44 | return try await makeRequest(request: request)
45 | }
46 |
47 | func destroyShop(id: String) async throws -> DestroyShopRequest.Response {
48 | try await makeRequest(request: DestroyShopRequest(id: id))
49 | }
50 |
51 | func shopDetail(id: String) async throws -> GetShopDetailRequest.Response {
52 | try await makeRequest(request: GetShopDetailRequest(id: id))
53 | }
54 |
55 | func makeShop(shop: Shop) async throws -> MakeShopRequest.Response {
56 | let request = MakeShopRequest(shop: shop)
57 | return try await makeRequest(request: request)
58 | }
59 |
60 | func resetShop(id: String) async throws -> ResetShopRequest.Response {
61 | try await makeRequest(request: ResetShopRequest(id: id))
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Persistence/KeychainStore/KeychainStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeychainStore.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2020/04/12.
6 | // Copyright © 2024 Daisuke Adachi All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import KeychainAccess
11 |
12 | enum KeychainStoreError: Error {
13 | case secCallFailed(Error)
14 | case notFound
15 | case badData
16 | case archiveFailure(Error)
17 | }
18 |
19 | protocol KeychainStore {
20 | associatedtype DataType: NSObject, NSCoding
21 |
22 | var account: String { get set }
23 | var service: String { get set }
24 |
25 | func remove() throws
26 | func retrieve() throws -> DataType
27 | func store(_ data: DataType) throws
28 | }
29 |
30 | extension KeychainStore {
31 | func remove() throws {
32 | let keychain = Keychain(service: service)
33 |
34 | do {
35 | try keychain.remove(account)
36 | } catch {
37 | throw KeychainStoreError.secCallFailed(error)
38 | }
39 | }
40 |
41 | func retrieve() throws -> DataType {
42 | let keychain = Keychain(service: service)
43 | let archived: Data?
44 |
45 | archived = try? keychain.getData(account)
46 |
47 | guard archived != nil else {
48 | throw KeychainStoreError.notFound
49 | }
50 |
51 | do {
52 | guard
53 | let unarchived = try NSKeyedUnarchiver.unarchivedObject(ofClass: DataType.self, from: archived!)
54 | else {
55 | throw KeychainStoreError.badData
56 | }
57 |
58 | return unarchived
59 | } catch {
60 | throw KeychainStoreError.archiveFailure(error)
61 | }
62 | }
63 |
64 | func store(_ data: DataType) throws {
65 | let archived: Data
66 | print("data: \(data)")
67 | do {
68 | archived = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true)
69 | } catch {
70 | throw KeychainStoreError.archiveFailure(error)
71 | }
72 |
73 | let keychain = Keychain(service: service)
74 |
75 | do {
76 | try keychain.set(archived, key: account)
77 | } catch {
78 | throw KeychainStoreError.secCallFailed(error)
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Persistence/KeychainStore/LoggedInShopkeeperKeychainStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoggedInShopkeeperStore.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2021/01/17.
6 | //
7 |
8 | import Foundation
9 |
10 | struct LoggedInShopkeeperKeychainStore: KeychainStore {
11 | // Make sure the account name doesn't match the bundle identifier!
12 | var account = String.keychainAccountLoggedInShopkeeper
13 | var service = String.keychainServiceLoggedInShopkeeper
14 |
15 | typealias DataType = LoggedInShopkeeper
16 | }
17 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/NativeAppTemplate/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryUserDefaults
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | CA92.1
13 |
14 |
15 |
16 | NSPrivacyTracking
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Sessions/SessionController+States.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import struct Foundation.Date
30 |
31 | extension SessionController {
32 | enum UserState: Sendable {
33 | case loggedIn
34 | case loggingIn
35 | case notLoggedIn
36 | }
37 |
38 | enum SessionState: Sendable {
39 | case unknown
40 | case online
41 | case offline
42 | }
43 |
44 | enum PermissionState: Equatable, Sendable {
45 | case notLoaded
46 | case loading
47 | case loaded
48 | case error
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Sessions/Shopkeeper+Backdoor.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import class Foundation.UserDefaults
30 |
31 | extension Shopkeeper {
32 | static var backdoor: Shopkeeper? {
33 | guard let backdoorToken = UserDefaults.standard.string(forKey: "shopkeeperBackdoorToken") else { return nil }
34 |
35 | let shopkeeperDict = [
36 | "id": "BACKDOOR_SHOPKEEPER",
37 | "email": "shopkeeper@nativeapptemplate.com",
38 | "name": "BACKDOORSHOPKEEPER",
39 | "uid": "uid",
40 | "token": backdoorToken,
41 | "client": "client",
42 | "expiry": "123456789"
43 | ]
44 |
45 | return Shopkeeper(dictionary: shopkeeperDict)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Styleguide/Color+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import SwiftUI
30 |
31 | extension Color {
32 | static var backgroundColor: Color {
33 | Color("backgroundColor")
34 | }
35 |
36 | static var snackError: Color {
37 | Color("error")
38 | }
39 |
40 | static var snackWarning: Color {
41 | Color("warning")
42 | }
43 |
44 | static var snackSuccess: Color {
45 | Color("success")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Styleguide/Inter/Inter-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Styleguide/Inter/Inter-Bold.ttf
--------------------------------------------------------------------------------
/NativeAppTemplate/Styleguide/Inter/Inter-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/NativeAppTemplate/Styleguide/Inter/Inter-Medium.ttf
--------------------------------------------------------------------------------
/NativeAppTemplate/Styleguide/UIColor+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import UIKit
30 |
31 | extension UIColor {
32 | static var backgroundUiColor: UIColor {
33 | UIColor(named: "backgroundColor")!
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Styleguide/UIFont+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import UIKit
30 |
31 | extension UIFont {
32 | static var uiLargeTitle: UIFont {
33 | .init(name: "Inter-Bold", size: 36.0)!
34 | }
35 | static var uiHeadline: UIFont {
36 | .init(name: "Inter-Medium", size: 18.0)!
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/App Root/AcceptPrivacyView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AcceptPrivacyView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/12/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AcceptPrivacyView: View {
11 | @Environment(\.dismiss) private var dismiss
12 | @Environment(MessageBus.self) private var messageBus
13 | @Environment(SessionController.self) private var sessionController
14 | @Binding var arePrivacyAccepted: Bool
15 | @State private var isUpdating = false
16 |
17 | var body: some View {
18 | contentView
19 | }
20 | }
21 |
22 | // MARK: - private
23 | private extension AcceptPrivacyView {
24 | var contentView: some View {
25 |
26 | @ViewBuilder var contentView: some View {
27 | if isUpdating {
28 | LoadingView()
29 | } else {
30 | acceptPrivacyView
31 | }
32 | }
33 |
34 | return contentView
35 | }
36 |
37 | var acceptPrivacyView: some View {
38 | VStack {
39 | let agreement = "Please accept updated [\(String.privacyPolicy)](\(String.privacyPolicyUrl))."
40 | Text(.init(agreement))
41 | .padding(.top, 48)
42 |
43 | MainButtonView(title: String.accept, type: .primary(withArrow: false)) {
44 | updateConfirmedPrivacyVersion()
45 | }
46 | .padding(24)
47 |
48 | Spacer()
49 | }
50 | .navigationTitle(String.privacyPolicyUpdated)
51 | .navigationBarTitleDisplayMode(.inline)
52 | }
53 |
54 | private func updateConfirmedPrivacyVersion() {
55 | Task { @MainActor in
56 | do {
57 | isUpdating = true
58 | try await sessionController.updateConfirmedPrivacyVersion()
59 | messageBus.post(message: Message(level: .success, message: .confirmedPrivacyVersionUpdated))
60 | } catch {
61 | messageBus.post(message: Message(level: .error, message: "\(String.confirmedPrivacyVersionUpdatedError) \(error.localizedDescription)", autoDismiss: false))
62 | }
63 |
64 | arePrivacyAccepted = true
65 | dismiss()
66 | }
67 | }
68 | }
69 |
70 | #Preview {
71 | @Previewable @State var arePrivacyAccepted = true
72 |
73 | return AcceptPrivacyView(arePrivacyAccepted: $arePrivacyAccepted)
74 | }
75 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/App Root/AcceptTermsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AcceptTermsView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/12/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AcceptTermsView: View {
11 | @Environment(\.dismiss) private var dismiss
12 | @Environment(MessageBus.self) private var messageBus
13 | @Environment(SessionController.self) private var sessionController
14 | @Binding var areTermsAccepted: Bool
15 | @State private var isUpdating = false
16 |
17 | var body: some View {
18 | contentView
19 | }
20 | }
21 |
22 | // MARK: - private
23 | private extension AcceptTermsView {
24 | var contentView: some View {
25 |
26 | @ViewBuilder var contentView: some View {
27 | if isUpdating {
28 | LoadingView()
29 | } else {
30 | acceptTermsView
31 | }
32 | }
33 |
34 | return contentView
35 | }
36 |
37 | var acceptTermsView: some View {
38 | VStack {
39 | let agreement = "Please accept updated [\(String.termsOfUse)](\(String.termsOfUseUrl))."
40 | Text(.init(agreement))
41 | .padding(.top, 48)
42 |
43 | MainButtonView(title: String.accept, type: .primary(withArrow: false)) {
44 | updateConfirmedTermsVersion()
45 | }
46 | .padding(24)
47 |
48 | Spacer()
49 | }
50 | .navigationTitle(String.termsOfUseUpdated)
51 | .navigationBarTitleDisplayMode(.inline)
52 | }
53 |
54 | private func updateConfirmedTermsVersion() {
55 | Task { @MainActor in
56 | do {
57 | isUpdating = true
58 | try await sessionController.updateConfirmedTermsVersion()
59 | messageBus.post(message: Message(level: .success, message: .confirmedTermsVersionUpdated))
60 | } catch {
61 | messageBus.post(message: Message(level: .error, message: "\(String.confirmedTermsVersionUpdatedError) \(error.localizedDescription)", autoDismiss: false))
62 | }
63 |
64 | areTermsAccepted = true
65 | dismiss()
66 | }
67 | }
68 | }
69 |
70 | #Preview {
71 | @Previewable @State var areTermsAccepted = true
72 |
73 | return AcceptTermsView(areTermsAccepted: $areTermsAccepted)
74 | }
75 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/App Root/ForgotPasswordView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForgotPasswordView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/03/02.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ForgotPasswordView: View {
11 | @Environment(\.dismiss) private var dismiss
12 | @Environment(MessageBus.self) private var messageBus
13 | @State var email: String = ""
14 | @State private var isSendingResetPasswordInstructions = false
15 | let signUpRepository: SignUpRepository
16 |
17 | init(
18 | signUpRepository: SignUpRepository
19 | ) {
20 | self.signUpRepository = signUpRepository
21 | }
22 |
23 | private var hasInvalidData: Bool {
24 | if Utility.isBlank(email) {
25 | return true
26 | }
27 |
28 | if !Utility.validateEmail(email) {
29 | return true
30 | }
31 |
32 | return false
33 | }
34 | }
35 |
36 | extension ForgotPasswordView {
37 | var body: some View {
38 | contentView
39 | }
40 | }
41 |
42 | // MARK: - private
43 | private extension ForgotPasswordView {
44 | var contentView: some View {
45 |
46 | @ViewBuilder var contentView: some View {
47 | if isSendingResetPasswordInstructions {
48 | LoadingView()
49 | } else {
50 | forgotPasswordView
51 | }
52 | }
53 |
54 | return contentView
55 | }
56 |
57 | var forgotPasswordView: some View {
58 | Form {
59 | Section {
60 | TextField(String.placeholderEmail, text: $email)
61 | .textContentType(.emailAddress)
62 | .autocapitalization(.none)
63 | } header: {
64 | Text(String.email)
65 | } footer: {
66 | if Utility.isBlank(email) {
67 | Text(String.emailIsRequired)
68 | .foregroundStyle(.red)
69 | } else if !Utility.validateEmail(email) {
70 | Text(String.emailIsInvalid)
71 | .foregroundStyle(.red)
72 | }
73 | }
74 |
75 | MainButtonView(title: String.buttonSendMeResetPasswordInstructions, type: .primary(withArrow: false)) {
76 | sendMeResetPasswordInstructionsTapped()
77 | }
78 | .disabled(hasInvalidData)
79 | .listRowBackground(Color.clear)
80 | }
81 | .navigationTitle(String.forgotYourPassword)
82 | }
83 |
84 | private func sendMeResetPasswordInstructionsTapped() {
85 | let whitespacesAndNewlines = CharacterSet.whitespacesAndNewlines
86 | let theEmail = email.trimmingCharacters(in: whitespacesAndNewlines)
87 |
88 | Task { @MainActor in
89 | do {
90 | let sendResetPassword = SendResetPassword(email: theEmail)
91 | try await signUpRepository.sendResetPasswordInstruction(sendResetPassword: sendResetPassword)
92 | messageBus.post(message: Message(level: .success, message: .sentResetPasswordInstruction, autoDismiss: false))
93 | dismiss()
94 | } catch {
95 | UIApplication.dismissKeyboard()
96 | messageBus.post(message: Message(level: .error, message: String.sentResetPasswordInstructionError, autoDismiss: false))
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/App Root/MessageBarView.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import SwiftUI
30 |
31 | extension AnyTransition {
32 | static var moveAndFade: AnyTransition {
33 | AnyTransition.move(edge: .bottom)
34 | .combined(with: .opacity)
35 | }
36 | }
37 |
38 | struct MessageBarView: View {
39 | @Bindable var messageBus: MessageBus
40 |
41 | var body: some View {
42 | VStack {
43 | if messageBus.messageVisible {
44 | SnackbarView(
45 | state: messageBus.currentMessage!.snackbarState,
46 | visible: $messageBus.messageVisible
47 | )
48 | }
49 | }
50 | .transition(.moveAndFade)
51 | .animation(.default, value: messageBus.messageVisible)
52 | }
53 | }
54 |
55 | struct MessageBarView_Previews: PreviewProvider {
56 | static var previews: some View {
57 | let messageBus = MessageBus()
58 | messageBus.post(message: Message(level: .warning, message: "This is a warning"))
59 |
60 | return VStack {
61 | Button(action: {
62 | messageBus.messageVisible.toggle()
63 | }) {
64 | Text(verbatim: "Show/Hide")
65 | }
66 |
67 | Button(action: {
68 | messageBus.post(message: Message(level: .success, message: "Button clicked!"))
69 | }) {
70 | Text(verbatim: "Post new message")
71 | }
72 |
73 | MessageBarView(messageBus: messageBus)
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/App Root/PermissionsLoadingView.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import SwiftUI
30 |
31 | struct PermissionsLoadingView: View {
32 | @Environment(SessionController.self) private var sessionController
33 | @State private var isShowingLogoutAlert = false
34 |
35 | var body: some View {
36 | LoadingView()
37 | .onTapGesture(count: 5) {
38 | isShowingLogoutAlert.toggle()
39 | }
40 | .alert(
41 | String.forceSignOut,
42 | isPresented: $isShowingLogoutAlert
43 | ) {
44 | Button(role: .destructive) {
45 | logout()
46 | } label: {
47 | Text(String.signOut)
48 | }
49 | }
50 | }
51 |
52 | func logout() {
53 | Task {
54 | try await sessionController.logout()
55 | }
56 | }
57 | }
58 |
59 | struct PermissionsLoadingView_Previews: PreviewProvider {
60 | static var previews: some View {
61 | PermissionsLoadingView()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/App Root/ResendConfirmationInstructionsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResendConfirmationInstructionsView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/09/30.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ResendConfirmationInstructionsView: View {
11 | @Environment(\.dismiss) private var dismiss
12 | @Environment(MessageBus.self) private var messageBus
13 | @State var email: String = ""
14 | @State private var isSendingConfirmationInstructions = false
15 | let signUpRepository: SignUpRepository
16 |
17 | init(
18 | signUpRepository: SignUpRepository
19 | ) {
20 | self.signUpRepository = signUpRepository
21 | }
22 |
23 | private var hasInvalidData: Bool {
24 | if Utility.isBlank(email) {
25 | return true
26 | }
27 |
28 | if !Utility.validateEmail(email) {
29 | return true
30 | }
31 |
32 | return false
33 | }
34 | }
35 |
36 | extension ResendConfirmationInstructionsView {
37 | var body: some View {
38 | contentView
39 | }
40 | }
41 |
42 | // MARK: - private
43 | private extension ResendConfirmationInstructionsView {
44 | var contentView: some View {
45 |
46 | @ViewBuilder var contentView: some View {
47 | if isSendingConfirmationInstructions {
48 | LoadingView()
49 | } else {
50 | resendConfirmationInstructionsView
51 | }
52 | }
53 |
54 | return contentView
55 | }
56 |
57 | var resendConfirmationInstructionsView: some View {
58 | Form {
59 | Section {
60 | TextField(String.placeholderEmail, text: $email)
61 | .textContentType(.emailAddress)
62 | .autocapitalization(.none)
63 | } header: {
64 | Text(String.email)
65 | } footer: {
66 | if Utility.isBlank(email) {
67 | Text(String.emailIsRequired)
68 | .foregroundStyle(.red)
69 | } else if !Utility.validateEmail(email) {
70 | Text(String.emailIsInvalid)
71 | .foregroundStyle(.red)
72 | }
73 | }
74 |
75 | MainButtonView(title: String.buttonSendMeConfirmationInstructions, type: .primary(withArrow: false)) {
76 | sendMeConfirmationInstructionsTapped()
77 | }
78 | .disabled(hasInvalidData)
79 | .listRowBackground(Color.clear)
80 | }
81 | .navigationTitle(String.didntReceiveConfirmationInstructions)
82 | }
83 |
84 | private func sendMeConfirmationInstructionsTapped() {
85 | let whitespacesAndNewlines = CharacterSet.whitespacesAndNewlines
86 | let theEmail = email.trimmingCharacters(in: whitespacesAndNewlines)
87 |
88 | Task { @MainActor in
89 | do {
90 | let sendConfirmation = SendConfirmation(email: theEmail)
91 | try await signUpRepository.sendConfirmationInstruction(sendConfirmation: sendConfirmation)
92 | messageBus.post(message: Message(level: .success, message: .sentConfirmationInstruction, autoDismiss: false))
93 | dismiss()
94 | } catch {
95 | UIApplication.dismissKeyboard()
96 | messageBus.post(message: Message(level: .error, message: String.sentConfirmationInstructionError, autoDismiss: false))
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/App Root/SignUpOrSignInView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignUpOrSignInView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2024/01/16.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SignUpOrSignInView: View {
11 | var body: some View {
12 | contentView
13 | }
14 | }
15 |
16 | // MARK: - private
17 | private extension SignUpOrSignInView {
18 | var contentView: some View {
19 | @ViewBuilder var contentView: some View {
20 | ScrollView {
21 | VStack {
22 | Image("logo")
23 | .resizable()
24 | .aspectRatio(contentMode: .fit)
25 | .frame(width: 384, height: 24)
26 | .padding()
27 |
28 | Image("onboarding1Slim")
29 | .resizable()
30 | .aspectRatio(contentMode: .fit)
31 | .frame(height: 256)
32 | .padding()
33 |
34 | let agreement = "By signing up or signing in, you agree to the [\(String.termsOfUse)](\(String.termsOfUseUrl)) and [\(String.privacyPolicy)](\(String.privacyPolicyUrl))."
35 | Text(.init(agreement))
36 | .padding(.top, 16)
37 | .padding(.horizontal, 24)
38 |
39 | VStack {
40 | NavigationLink(destination: SignUpView(signUpRepository: SignUpRepository())) {
41 | MainButtonImageView(title: String.signUpForAnAccount, type: .primary(withArrow: false))
42 | .padding(.top, 8)
43 | .padding(.horizontal, 24)
44 | }
45 |
46 | Text(verbatim: "or")
47 | .padding(.top, 8)
48 |
49 | NavigationLink(destination: SignInEmailAndPasswordView(signUpRepository: SignUpRepository())) {
50 | Text(String.signInToYourAccount)
51 | .font(.uiLabel)
52 | }
53 | .padding(.top, 8)
54 | }
55 | .padding(.top, 4)
56 |
57 | Spacer()
58 | }
59 | .padding(.bottom)
60 | }
61 | .navigationBarTitleDisplayMode(.inline)
62 | .toolbar {
63 | ToolbarItem(placement: .navigationBarTrailing) {
64 | Link(String.supportWebsite, destination: URL(string: String.supportWebsiteUrl)!)
65 | }
66 | }
67 | .background(Color.backgroundColor)
68 | }
69 |
70 | return contentView
71 | }
72 | }
73 |
74 | #Preview {
75 | SignUpOrSignInView()
76 | }
77 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Empty States/LoadingView.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import SwiftUI
30 |
31 | struct LoadingView: View {
32 | var body: some View {
33 | VStack {
34 | ProgressView().scaleEffect(1.0, anchor: .center)
35 | .padding([.bottom], 12)
36 | Text(String.loading)
37 | .font(.uiHeadline)
38 | }
39 | }
40 | }
41 |
42 | struct LoadingView_Previews: PreviewProvider {
43 | static var previews: some View {
44 | LoadingView().inAllColorSchemes
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Empty States/NeedAppUpdatesView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NeedAppUpdatesView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/12/20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct NeedAppUpdatesView: View {
11 | @Environment(\.openURL) var openURL
12 |
13 | var body: some View {
14 | VStack {
15 | Image(systemName: "exclamationmark.arrow.circlepath")
16 | .resizable()
17 | .aspectRatio(contentMode: .fit)
18 | .frame(width: 96)
19 | .foregroundStyle(.titleText)
20 | .padding()
21 | Text(String.updateApp)
22 | .font(.uiTitle1)
23 | .foregroundStyle(.titleText)
24 | .padding(.top)
25 | Text(String.installNewVersionApp)
26 | .foregroundStyle(.contentText)
27 | .padding(.top, 4)
28 | Button {
29 | openURL(URL(string: String.appStoreUrl)!)
30 | } label: {
31 | Text(String.updateApp)
32 | }
33 | .padding(.top)
34 | }
35 | .frame(maxWidth: .infinity, maxHeight: .infinity)
36 | .background(Color.backgroundColor)
37 | .edgesIgnoringSafeArea(.all)
38 | }
39 | }
40 |
41 | #Preview {
42 | NeedAppUpdatesView()
43 | }
44 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Empty States/OfflineView.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 Razeware LLC
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
14 | // distribute, sublicense, create a derivative work, and/or sell copies of the
15 | // Software in any work that is designed, intended, or marketed for pedagogical or
16 | // instructional purposes related to programming, coding, application development,
17 | // or information technology. Permission for such use, copying, modification,
18 | // merger, publication, distribution, sublicensing, creation of derivative works,
19 | // or sale is expressly withheld.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | // THE SOFTWARE.
28 |
29 | import SwiftUI
30 |
31 | struct OfflineView: View {
32 | var body: some View {
33 | VStack {
34 | Image(systemName: "wifi.slash")
35 | .resizable()
36 | .aspectRatio(contentMode: .fit)
37 | .frame(width: 96)
38 | .padding()
39 | .foregroundStyle(.titleText)
40 |
41 | Text(String.noConnection)
42 | .font(.uiTitle1)
43 | .foregroundStyle(.titleText)
44 | .multilineTextAlignment(.center)
45 | .padding(.top)
46 |
47 | Text(String.checkInternetConnection)
48 | .font(.uiLabel)
49 | .lineSpacing(8)
50 | .foregroundStyle(.contentText)
51 | .multilineTextAlignment(.center)
52 | .padding(.top, 4)
53 | .padding(.horizontal, 32)
54 | }
55 | .frame(maxWidth: .infinity, maxHeight: .infinity)
56 | .background(Color.backgroundColor)
57 | .edgesIgnoringSafeArea(.all)
58 | }
59 | }
60 |
61 | struct OfflineView_Previews: PreviewProvider {
62 | static var previews: some View {
63 | OfflineView()
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Scan/CompleteScanResultView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CompleteScanResultView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CompleteScanResultView: View {
11 | @Environment(\.dismiss) private var dismiss
12 | @Environment(MessageBus.self) private var messageBus
13 | var completeScanResult: CompleteScanResult
14 |
15 | init(
16 | completeScanResult: CompleteScanResult
17 | ) {
18 | self.completeScanResult = completeScanResult
19 | }
20 |
21 | var body: some View {
22 | contentView
23 | }
24 | }
25 |
26 | // MARK: - private
27 | private extension CompleteScanResultView {
28 | var contentView: some View {
29 |
30 | @ViewBuilder var contentView: some View {
31 | switch completeScanResult.type {
32 | case .completed, .reset:
33 | succeededView
34 | case .failed:
35 | failedView
36 | case .idled:
37 | idledView
38 | }
39 | }
40 |
41 | return contentView
42 | }
43 |
44 | @ViewBuilder var succeededView: some View {
45 | if let itemTag = completeScanResult.itemTag {
46 | GroupBox(label: Label(String("Result"), systemImage: "checkmark.circle") ) {
47 | Text(String(itemTag.queueNumber))
48 | .font(.uiTitle1)
49 |
50 | if itemTag.state == .completed {
51 | CompletedTag()
52 | } else {
53 | IdlingTag()
54 | }
55 |
56 | if completeScanResult.type == .reset {
57 | Text(completeScanResult.type.displayString)
58 | }
59 |
60 | HStack(alignment: .firstTextBaseline) {
61 | Text(completeScanResult.scannedAt.cardTimeAgoInWordsDateString)
62 | .font(.uiBodyCustom)
63 | .foregroundStyle(.successSecondaryForeground)
64 | Text(verbatim: "complete scanned")
65 | .font(.uiFootnote)
66 | .foregroundStyle(.successSecondaryForeground)
67 | }
68 | .padding(.top, 8)
69 | }
70 | .backgroundStyle(.successBackground)
71 | }
72 | }
73 |
74 | @ViewBuilder var failedView: some View {
75 | GroupBox(label: Label(String("Error"), systemImage: "exclamationmark.triangle") ) {
76 | Text(completeScanResult.message)
77 | }
78 | .backgroundStyle(.failureBackground)
79 | }
80 |
81 | @ViewBuilder var idledView: some View {
82 | GroupBox(label: Label(String("Result"), systemImage: "checkmark.circle") ) {
83 | }
84 | .backgroundStyle(.successBackground)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shared/Tags/CompletedTag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CompletedTag.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CompletedTag: View {
11 | var body: some View {
12 | TagView(
13 | text: "completed",
14 | textColor: .completedTagForeground,
15 | backgroundColor: .completedTagBackground,
16 | borderColor: .completedTagBorder
17 | )
18 | }
19 | }
20 |
21 | struct CompletedTag_Previews: PreviewProvider {
22 | static var previews: some View {
23 | VStack(spacing: 12) {
24 | completedTag.colorScheme(.light)
25 | completedTag.colorScheme(.dark)
26 | }
27 | }
28 |
29 | static var completedTag: some View {
30 | CompletedTag()
31 | .padding()
32 | .background(Color.backgroundColor)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shared/Tags/CustomerScannedTag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomerScannedTag.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CustomerScannedTag: View {
11 | var body: some View {
12 | TagView(
13 | text: "customer scanned",
14 | textColor: .customerScannedTagForeground,
15 | backgroundColor: .customerScannedTagBackground,
16 | borderColor: .customerScannedTagBorder
17 | )
18 | }
19 | }
20 |
21 | struct CustomerScannedTag_Previews: PreviewProvider {
22 | static var previews: some View {
23 | VStack(spacing: 12) {
24 | customerScannedTag.colorScheme(.light)
25 | customerScannedTag.colorScheme(.dark)
26 | }
27 | }
28 |
29 | static var customerScannedTag: some View {
30 | CustomerScannedTag()
31 | .padding()
32 | .background(Color.backgroundColor)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shared/Tags/IdlingTagView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IdlingTag.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct IdlingTag: View {
11 | var body: some View {
12 | TagView(
13 | text: "idling",
14 | textColor: .idlingTagForeground,
15 | backgroundColor: .idlingTagBackground,
16 | borderColor: .idlingTagBorder
17 | )
18 | }
19 | }
20 |
21 | struct IdlingTag_Previews: PreviewProvider {
22 | static var previews: some View {
23 | VStack(spacing: 12) {
24 | idlingTag.colorScheme(.light)
25 | idlingTag.colorScheme(.dark)
26 | }
27 | }
28 |
29 | static var idlingTag: some View {
30 | IdlingTag()
31 | .padding()
32 | .background(Color.backgroundColor)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shared/Tags/TagView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TagView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/01/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TagView: View {
11 | private static let defaultIconHeight: CGFloat = 12.0
12 |
13 | private struct SizeKey: PreferenceKey {
14 | static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
15 | value = value ?? nextValue()
16 | }
17 | }
18 |
19 | @State private var height: CGFloat?
20 |
21 | let text: String
22 | let textColor: Color
23 | let backgroundColor: Color
24 | let borderColor: Color
25 | var image: Image?
26 |
27 | var body: some View {
28 | HStack(spacing: 4) {
29 | image?
30 | .resizable()
31 | .aspectRatio(contentMode: .fit)
32 | .foregroundStyle(textColor)
33 | .frame(height: Self.defaultIconHeight)
34 |
35 | Text(text.uppercased())
36 | .foregroundStyle(textColor)
37 | .font(.uiUppercaseTag)
38 | .kerning(0.5)
39 | .background(
40 | GeometryReader { proxy in
41 | Color.clear.preference(key: SizeKey.self, value: proxy.size)
42 | }
43 | )
44 | }
45 | .padding([.vertical], 4)
46 | .padding([.horizontal], 8)
47 | .background(backgroundColor)
48 | .cornerRadius(4) // This is a bit hacky.
49 | .onPreferenceChange(SizeKey.self) { size in
50 | Task { @MainActor in
51 | height = size?.height
52 | }
53 | }
54 | }
55 | }
56 |
57 | struct TagView_Previews: PreviewProvider {
58 | static var previews: some View {
59 | VStack(spacing: 18) {
60 | TagView(
61 | text: "this is a tag",
62 | textColor: .white,
63 | backgroundColor: .red,
64 | borderColor: .yellow
65 | )
66 |
67 | TagView(
68 | text: "with an image",
69 | textColor: .white,
70 | backgroundColor: .red,
71 | borderColor: .yellow,
72 | image: Image(systemName: "checkmark")
73 | )
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shop Detail/ShopDetailCardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopDetailCardView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ShopDetailCardView: View {
11 | let itemTag: ItemTag
12 |
13 | init(
14 | itemTag: ItemTag
15 | ) {
16 | self.itemTag = itemTag
17 | }
18 |
19 | var body: some View {
20 | content
21 | }
22 |
23 | var content: some View {
24 | HStack {
25 | Text(String(itemTag.queueNumber))
26 | .font(.uiTitle4)
27 |
28 | Spacer()
29 |
30 | VStack(alignment: .trailing) {
31 | if itemTag.scanState == ScanState.scanned {
32 | CustomerScannedTag()
33 |
34 | if let customerReadAt = itemTag.customerReadAt {
35 | Text(customerReadAt.cardTimeString)
36 | .font(.uiFootnote)
37 | .foregroundStyle(.contentText)
38 | }
39 | }
40 | }
41 |
42 | Spacer()
43 |
44 | VStack(alignment: .trailing) {
45 | if itemTag.state == .completed {
46 | CompletedTag()
47 |
48 | if let completedAt = itemTag.completedAt {
49 | Text(completedAt.cardTimeString)
50 | .font(.uiFootnote)
51 | .foregroundStyle(.contentText)
52 | }
53 | } else {
54 | IdlingTag()
55 | }
56 | }
57 | .frame(minWidth: 82, alignment: .trailing)
58 | }
59 | .frame(minHeight: 48)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shop List/ItemTag List/ItemTagListCardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagListCardView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ItemTagListCardView: View {
11 | let itemTag: ItemTag
12 |
13 | var body: some View {
14 | Text(String(itemTag.queueNumber))
15 | .font(.uiTitle4)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shop List/ShopListCardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopListCardView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/02/05.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ShopListCardView: View {
11 | let shop: Shop
12 |
13 | var body: some View {
14 | VStack(alignment: .leading) {
15 | Text(shop.name)
16 | .font(.uiTitle4)
17 | .foregroundStyle(.accent)
18 |
19 | let statImageSize = 12.0
20 |
21 | Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 12, verticalSpacing: 4) {
22 | GridRow {
23 | Image(systemName: "person.2")
24 | .frame(width: statImageSize, height: statImageSize)
25 | .foregroundStyle(.secondaryText)
26 | Text(String(shop.scannedItemTagsCount))
27 | .font(.uiLabelBold)
28 | .gridColumnAlignment(.trailing)
29 | Text(verbatim: "tags scanned by customers")
30 | .font(.uiFootnote)
31 | .foregroundStyle(.contentText)
32 | }
33 |
34 | GridRow {
35 | Image(systemName: "flag.checkered")
36 | .frame(width: statImageSize, height: statImageSize)
37 | .foregroundStyle(.secondaryText)
38 | Text(String(shop.completedItemTagsCount))
39 | .font(.uiLabelBold)
40 | Text(verbatim: "completed tags")
41 | .font(.uiFootnote)
42 | .foregroundStyle(.contentText)
43 | }
44 |
45 | GridRow {
46 | Image(systemName: "rectangle.stack")
47 | .frame(width: statImageSize, height: statImageSize)
48 | .foregroundStyle(.secondaryText)
49 | Text(String(shop.itemTagsCount))
50 | .font(.uiLabelBold)
51 | Text(verbatim: "all tags")
52 | .font(.uiFootnote)
53 | .foregroundStyle(.contentText)
54 | }
55 | }
56 | .padding(.top)
57 |
58 | Text(shop.description)
59 | .font(.uiCaption)
60 | .foregroundStyle(.contentText)
61 | .padding(.top)
62 | }
63 | .padding()
64 | .dynamicTypeSize(...DynamicTypeSize.accessibility1)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/Shop Settings/NumberTagsWebpageListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NumberTagsWebpageList.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 | import UniformTypeIdentifiers
10 |
11 | enum NumberTagsWebpageListType: String, Identifiable, CaseIterable, Codable, Hashable {
12 | case server
13 |
14 | var id: Self { self }
15 |
16 | var displayString: String {
17 | switch self {
18 | case .server:
19 | return String.serverNumberTagsWebpage
20 | }
21 | }
22 | }
23 |
24 | struct NumberTagsWebpageListView: View {
25 | @Environment(MessageBus.self) private var messageBus
26 | private var shop: Shop
27 |
28 | init(
29 | shop: Shop
30 | ) {
31 | self.shop = shop
32 | }
33 | }
34 |
35 | // MARK: - View
36 | extension NumberTagsWebpageListView {
37 | var body: some View {
38 | contentView
39 | }
40 | }
41 |
42 | // MARK: - private
43 | private extension NumberTagsWebpageListView {
44 | var contentView: some View {
45 |
46 | @ViewBuilder var contentView: some View {
47 | numberTagsWebpageListView
48 | }
49 |
50 | return contentView
51 | }
52 |
53 | var numberTagsWebpageListView: some View {
54 | VStack {
55 | Text(shop.name)
56 | .font(.uiTitle1)
57 | .foregroundStyle(.titleText)
58 | .padding(.top, 24)
59 | List(NumberTagsWebpageListType.allCases) { numberTagsWebpageListType in
60 | switch numberTagsWebpageListType {
61 | case .server:
62 | Section {
63 | Link(numberTagsWebpageListType.displayString, destination: shop.displayShopServerUrl)
64 | } header: {
65 | Label(String("Server"), systemImage: "storefront")
66 | } footer: {
67 | Button(String.copyWebpageUrl) {
68 | copyWebpageUrl(shop.displayShopServerUrl.absoluteString)
69 | }
70 | }
71 | .listRowBackground(Color.cardBackground)
72 | }
73 | }
74 | }
75 | .navigationTitle(String.shopSettingsNumberTagsWebpageLabel)
76 | }
77 |
78 | func copyWebpageUrl(_ url: String) {
79 | UIPasteboard.general.setValue(url, forPasteboardType: UTType.plainText.identifier)
80 | messageBus.post(message: Message(level: .success, message: .webpageUrlCopied))
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/NativeAppTemplate/UI/UIKit/MailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MailView.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2023/11/12.
6 | //
7 |
8 | import AVFoundation
9 | import Foundation
10 | import MessageUI
11 | import SwiftUI
12 | import UIKit
13 |
14 | struct MailView: UIViewControllerRepresentable {
15 | @Environment(\.presentationMode) var presentation
16 | @Binding var result: Result?
17 | var recipients = [String]()
18 | var subject = ""
19 | var messageBody = ""
20 | var isHTML = false
21 |
22 | class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
23 | @Binding var presentation: PresentationMode
24 | @Binding var result: Result?
25 |
26 | init(presentation: Binding,
27 | result: Binding?>) {
28 | _presentation = presentation
29 | _result = result
30 | }
31 |
32 | func mailComposeController(_: MFMailComposeViewController,
33 | didFinishWith result: MFMailComposeResult,
34 | error: Error?) {
35 | defer {
36 | $presentation.wrappedValue.dismiss()
37 | }
38 | guard error == nil else {
39 | self.result = .failure(error!)
40 | return
41 | }
42 | self.result = .success(result)
43 |
44 | if result == .sent {
45 | AudioServicesPlayAlertSound(SystemSoundID(1001))
46 | }
47 | }
48 | }
49 |
50 | func makeCoordinator() -> Coordinator {
51 | Coordinator(presentation: presentation,
52 | result: $result)
53 | }
54 |
55 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> MFMailComposeViewController {
56 | let mfMailComposeViewController = MFMailComposeViewController()
57 | mfMailComposeViewController.setToRecipients(recipients)
58 | mfMailComposeViewController.setSubject(subject)
59 | mfMailComposeViewController.setMessageBody(messageBody, isHTML: isHTML)
60 | mfMailComposeViewController.mailComposeDelegate = context.coordinator
61 | return mfMailComposeViewController
62 | }
63 |
64 | func updateUIViewController(_: MFMailComposeViewController,
65 | context _: UIViewControllerRepresentableContext) {}
66 | }
67 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Utilities/ImageSaver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageSaver.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import UIKit
9 |
10 | class ImageSaver: NSObject {
11 | private var completion: (_ error: Error?) -> Void = { _ in }
12 |
13 | func save(image: UIImage, completion: @escaping (_ error: Error?) -> Void) {
14 | self.completion = completion
15 | UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
16 | }
17 |
18 | @objc
19 | private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
20 | completion(error)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/NativeAppTemplate/Utilities/QRCodeGenerator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QRCodeGenerator.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct QRCodeGenerator {
11 | func generate(inputText: String, scale: CGFloat = 2, centerImage: UIImage?) -> UIImage? {
12 | guard let qrFilter = CIFilter(name: "CIQRCodeGenerator")
13 | else { return nil }
14 |
15 | let inputData = inputText.data(using: .utf8)
16 | qrFilter.setValue(inputData, forKey: "inputMessage")
17 | qrFilter.setValue("H", forKey: "inputCorrectionLevel")
18 |
19 | guard let ciImage = qrFilter.outputImage
20 | else { return nil }
21 |
22 | let sizeTransform = CGAffineTransform(scaleX: scale, y: scale)
23 | let scaledCiImage = ciImage.transformed(by: sizeTransform)
24 |
25 | let context = CIContext()
26 | guard let cgImage = context.createCGImage(scaledCiImage, from: scaledCiImage.extent)
27 | else { return nil }
28 |
29 | if let centerImage = centerImage {
30 | return UIImage(cgImage: cgImage).composited(withSmallCenterImage: centerImage)
31 | } else {
32 | return UIImage(cgImage: cgImage)
33 | }
34 | }
35 |
36 | func generateWithCenterText(inputText: String, scale: CGFloat = 2, centerText: String) -> UIImage? {
37 | if let centerImage = centerText.image(
38 | withAttributes: [
39 | .font: UIFont.systemFont(ofSize: 40.0),
40 | .backgroundColor: UIColor.white
41 | ]
42 | ) {
43 | return generate(inputText: inputText, scale: scale, centerImage: centerImage)
44 | } else {
45 | return generate(inputText: inputText, scale: scale, centerImage: nil)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/NativeAppTemplateTests/Models/ShopkeeperTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopkeeperTest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/01/31.
6 | //
7 |
8 | import Testing
9 | import SwiftyJSON
10 | @testable import NativeAppTemplate
11 |
12 | struct ShopkeeperTest {
13 | let shopkeeperDictionary = [
14 | "id": "5712F2DF-DFC7-A3AA-66BC-191203654A1A",
15 | "account_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1Z",
16 | "personal_account_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1Z",
17 | "account_owner_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1C",
18 | "account_name": "Account1",
19 | "email": "email@example.com",
20 | "name": "Jhon Smith",
21 | "time_zone": "Tokyo",
22 | "uid": "email@example.com",
23 | "token": "Sample.Token",
24 | "client": "Sample.Client",
25 | "expiry": "123456789"
26 | ]
27 |
28 | @Test func shopkeeperCorrectlyPopulatesWithDictionary() {
29 | guard let shopkeeper = Shopkeeper(dictionary: shopkeeperDictionary) else {
30 | Issue.record("Shopkeeper should be correctly populated")
31 | return
32 | }
33 |
34 | #expect(shopkeeperDictionary["id"] == shopkeeper.id)
35 | #expect(shopkeeperDictionary["account_id"] == shopkeeper.accountId)
36 | #expect(shopkeeperDictionary["personal_account_id"] == shopkeeper.personalAccountId)
37 | #expect(shopkeeperDictionary["account_owner_id"] == shopkeeper.accountOwnerId)
38 | #expect(shopkeeperDictionary["account_name"] == shopkeeper.accountName)
39 | #expect(shopkeeperDictionary["email"] == shopkeeper.email)
40 | #expect(shopkeeperDictionary["name"] == shopkeeper.name)
41 | #expect(shopkeeperDictionary["time_zone"] == shopkeeper.timeZone)
42 | #expect(shopkeeperDictionary["uid"] == shopkeeper.uid)
43 | #expect(shopkeeperDictionary["token"] == shopkeeper.token)
44 | #expect(shopkeeperDictionary["client"] == shopkeeper.client)
45 | #expect(shopkeeperDictionary["expiry"] == shopkeeper.expiry)
46 | }
47 |
48 | func shopkeeperDictionaryHasRequiredFields() {
49 | var invalidDictionary = shopkeeperDictionary
50 | invalidDictionary.removeValue(forKey: "id")
51 | let shopkeeper = Shopkeeper(dictionary: invalidDictionary)
52 |
53 | #expect(shopkeeper == nil)
54 | }
55 |
56 | func additionalEntriesInTheDictionaryAreIgnored() {
57 | var overSpecifiedDictionary = shopkeeperDictionary
58 | overSpecifiedDictionary["extra_field"] = "some-guff"
59 | let shopkeeper = Shopkeeper(dictionary: overSpecifiedDictionary)
60 |
61 | #expect(shopkeeper != nil)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/NativeAppTemplateTests/Networking/Adapters/ItemTagAdapterTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTagAdapterTest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/03/01.
6 | //
7 |
8 | import Testing
9 | import SwiftyJSON
10 | @testable import NativeAppTemplate
11 |
12 | struct ItemTagAdapterTest {
13 | let sampleResource: JSON = [
14 | "id": "5712F2DF-DFC7-A3AA-66BC-191203654A1A",
15 | "type": "item_tag",
16 | "attributes": [
17 | "shop_id": "88705252-2FD2-4414-9E85-E6888033294A",
18 | "queue_number": "A001",
19 | "state": "idled",
20 | "scan_state": "unscanned",
21 | "created_at": "2020-01-01T12:00:00.000Z",
22 | "shop_name": "Shop1",
23 | "customer_read_at": "2020-01-02T12:00:00.000Z",
24 | "completed_at": "2020-01-04T12:00:00.000Z",
25 | "already_completed": false
26 | ]
27 | ]
28 |
29 | func makeJsonAPIResource(for dict: JSON) throws -> JSONAPIResource {
30 | let json: JSON = [
31 | "data": [
32 | dict
33 | ]
34 | ]
35 |
36 | let document = JSONAPIDocument(json)
37 | return document.data.first!
38 | }
39 |
40 | @Test func validResourceProcessedCorrectly() async throws {
41 | let resource = try makeJsonAPIResource(for: sampleResource)
42 | let itemTag = try ItemTagAdapter.process(resource: resource)
43 | #expect("5712F2DF-DFC7-A3AA-66BC-191203654A1A" == itemTag.id)
44 | }
45 |
46 | @Test func inInvalidTypeThrows() throws {
47 | var sample = sampleResource
48 | sample["type"] = "invalid"
49 |
50 | let resource = try makeJsonAPIResource(for: sample)
51 |
52 | #expect { try ItemTagAdapter.process(resource: resource) } throws: { error in
53 | let entityAdapterError = error as? EntityAdapterError
54 | return EntityAdapterError.invalidResourceTypeForAdapter == entityAdapterError
55 | }
56 | }
57 |
58 | @Test func missingnAccountIdThrows() throws {
59 | var sample = sampleResource
60 | sample["attributes"].dictionaryObject?.removeValue(forKey: "shop_id")
61 |
62 | let resource = try makeJsonAPIResource(for: sample)
63 |
64 | #expect { try ItemTagAdapter.process(resource: resource) } throws: { error in
65 | let entityAdapterError = error as? EntityAdapterError
66 | return EntityAdapterError.invalidOrMissingAttributes == entityAdapterError
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/NativeAppTemplateTests/Networking/Adapters/ShopAdapterTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopAdapterTest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/01/31.
6 | //
7 |
8 | import Testing
9 | import SwiftyJSON
10 | @testable import NativeAppTemplate
11 |
12 | struct ShopAdapterTest {
13 | let sampleResource: JSON = [
14 | "id": "5712F2DF-DFC7-A3AA-66BC-191203654A1C",
15 | "type": "shop",
16 | "attributes": [
17 | "name": "Shop1",
18 | "description": "This is a Shop1",
19 | "time_zone": "Tokyo",
20 | "display_shop_server_path": "https://api.nativeapptemplate.com/display/shops/1ed7ea32-65d5-4e64-97a0-0e00b6cee8c3?type=server", // swiftlint:disable:this line_length
21 | "item_tags_count": 10,
22 | "scanned_item_tags_count": 1,
23 | "completed_item_tags_count": 2
24 | ],
25 | "relationships": [
26 | "account": [
27 | "data": [
28 | "id": "96C3444D-5B64-1EFF-2354-55787BD43277",
29 | "type": "Account1",
30 | "attributes": [
31 | "name": "Shop1",
32 | "owner_id": "88705252-2FD2-4414-9E85-E6888033294B",
33 | "personal": true,
34 | "is_admin": true,
35 | "owner_name": "Jhon Smith",
36 | "accounts_shopkeepers_count": 99,
37 | "accounts_invitations_count": 98,
38 | "shops_count": 96
39 | ]
40 | ]
41 | ]
42 | ],
43 | "meta": [
44 | "limit_count": 96,
45 | "created_shops_count": 3
46 | ]
47 | ]
48 |
49 | func makeJsonAPIResource(for dict: JSON) throws -> JSONAPIResource {
50 | let json: JSON = [
51 | "data": [
52 | dict
53 | ]
54 | ]
55 |
56 | let document = JSONAPIDocument(json)
57 | return document.data.first!
58 | }
59 |
60 | @Test func validResourceProcessedCorrectly() async throws {
61 | let resource = try makeJsonAPIResource(for: sampleResource)
62 | let shop = try ShopAdapter.process(resource: resource)
63 | #expect("5712F2DF-DFC7-A3AA-66BC-191203654A1C" == shop.id)
64 | }
65 |
66 | @Test func inInvalidTypeThrows() throws {
67 | var sample = sampleResource
68 | sample["type"] = "invalid"
69 |
70 | let resource = try makeJsonAPIResource(for: sample)
71 |
72 | #expect { try ShopAdapter.process(resource: resource) } throws: { error in
73 | let entityAdapterError = error as? EntityAdapterError
74 | return EntityAdapterError.invalidResourceTypeForAdapter == entityAdapterError
75 | }
76 | }
77 |
78 | @Test func missingnNameThrows() throws {
79 | var sample = sampleResource
80 | sample["attributes"].dictionaryObject?.removeValue(forKey: "name")
81 |
82 | let resource = try makeJsonAPIResource(for: sample)
83 |
84 | #expect { try ShopAdapter.process(resource: resource) } throws: { error in
85 | let entityAdapterError = error as? EntityAdapterError
86 | return EntityAdapterError.invalidOrMissingAttributes == entityAdapterError
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/NativeAppTemplateTests/Networking/Adapters/ShopkeeperAdapterTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopkeeperAdapterTest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/01/31.
6 | //
7 |
8 | import Testing
9 | import SwiftyJSON
10 | @testable import NativeAppTemplate
11 |
12 | struct ShopkeeperAdapterTest {
13 | let sampleResource: JSON = [
14 | "id": "5712F2DF-DFC7-A3AA-66BC-191203654A1C",
15 | "type": "shopkeeper",
16 | "attributes": [
17 | "name": "Shopkeeper1",
18 | "email": "email@example.com",
19 | "time_zone": "Tokyo"
20 | ]
21 | ]
22 |
23 | func makeJsonAPIResource(for dict: JSON) throws -> JSONAPIResource {
24 | let json: JSON = [
25 | "data": [
26 | dict
27 | ]
28 | ]
29 |
30 | let document = JSONAPIDocument(json)
31 | return document.data.first!
32 | }
33 |
34 | @Test func validResourceProcessedCorrectly() async throws {
35 | let resource = try makeJsonAPIResource(for: sampleResource)
36 | let shopkeeper = try ShopkeeperAdapter.process(resource: resource)
37 | #expect("5712F2DF-DFC7-A3AA-66BC-191203654A1C" == shopkeeper.id)
38 | }
39 |
40 | @Test func inInvalidTypeThrows() throws {
41 | var sample = sampleResource
42 | sample["type"] = "invalid"
43 |
44 | let resource = try makeJsonAPIResource(for: sample)
45 |
46 | #expect { try ShopkeeperAdapter.process(resource: resource) } throws: { error in
47 | let entityAdapterError = error as? EntityAdapterError
48 | return EntityAdapterError.invalidResourceTypeForAdapter == entityAdapterError
49 | }
50 | }
51 |
52 | @Test func missingnNameThrows() throws {
53 | var sample = sampleResource
54 | sample["attributes"].dictionaryObject?.removeValue(forKey: "name")
55 |
56 | let resource = try makeJsonAPIResource(for: sample)
57 |
58 | #expect { try ShopkeeperAdapter.process(resource: resource) } throws: { error in
59 | let entityAdapterError = error as? EntityAdapterError
60 | return EntityAdapterError.invalidOrMissingAttributes == entityAdapterError
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/NativeAppTemplateTests/Networking/Adapters/ShopkeeperSignInAdapterTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShopkeeperSignInAdapterTest.swift
3 | // NativeAppTemplate
4 | //
5 | // Created by Daisuke Adachi on 2025/01/31.
6 | //
7 |
8 | import Testing
9 | import SwiftyJSON
10 | @testable import NativeAppTemplate
11 |
12 | struct ShopkeeperSignInAdapterTest {
13 | let sampleResource: JSON = [
14 | "id": "5712F2DF-DFC7-A3AA-66BC-191203654A1C",
15 | "type": "shopkeeper_sign_in",
16 | "attributes": [
17 | "account_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1Z",
18 | "personal_account_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1Z",
19 | "account_owner_id": "5712F2DF-DFC7-A3AA-66BC-191203654A1C",
20 | "account_name": "Account1",
21 | "email": "email@example.com",
22 | "name": "Jhon Smith",
23 | "time_zone": "Tokyo",
24 | "uid": "email@example.com"
25 | ]
26 | ]
27 |
28 | func makeJsonAPIResource(for dict: JSON) throws -> JSONAPIResource {
29 | let json: JSON = [
30 | "data": [
31 | dict
32 | ]
33 | ]
34 |
35 | let document = JSONAPIDocument(json)
36 | return document.data.first!
37 | }
38 |
39 | @Test func validResourceProcessedCorrectly() async throws {
40 | let resource = try makeJsonAPIResource(for: sampleResource)
41 | let shopkeeper = try ShopkeeperSignInAdapter.process(resource: resource)
42 | #expect("5712F2DF-DFC7-A3AA-66BC-191203654A1C" == shopkeeper.id)
43 | }
44 |
45 | @Test func inInvalidTypeThrows() throws {
46 | var sample = sampleResource
47 | sample["type"] = "invalid"
48 |
49 | let resource = try makeJsonAPIResource(for: sample)
50 |
51 | #expect { try ShopkeeperSignInAdapter.process(resource: resource) } throws: { error in
52 | let entityAdapterError = error as? EntityAdapterError
53 | return EntityAdapterError.invalidResourceTypeForAdapter == entityAdapterError
54 | }
55 | }
56 |
57 | @Test func missingnNameThrows() throws {
58 | var sample = sampleResource
59 | sample["attributes"].dictionaryObject?.removeValue(forKey: "name")
60 |
61 | let resource = try makeJsonAPIResource(for: sample)
62 |
63 | #expect { try ShopkeeperSignInAdapter.process(resource: resource) } throws: { error in
64 | let entityAdapterError = error as? EntityAdapterError
65 | return EntityAdapterError.invalidOrMissingAttributes == entityAdapterError
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If you come across a security vulnerability in __nativeapptemplate__, or any of the __nativeapptemplate__.com infrastructure that it connects to, please do not file an
6 | issue on GitHub.
7 |
8 | Instead, please document your issue as fully as you can, and email your issue report directly to support@nativeapptemplate.com.
9 |
10 | We take security very seriously, and will assess any reports first before we embark upon getting them fixed as soon as possible.
11 |
12 | Thanks!
13 |
--------------------------------------------------------------------------------
/SampleCode.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // See the LICENSE.txt file for this sample’s licensing information.
3 | //
4 | // SampleCode.xcconfig
5 | //
6 |
7 | // The `SAMPLE_CODE_DISAMBIGUATOR` configuration is to make it easier to build
8 | // and run a sample code project. Once you set your project's development team,
9 | // you'll have a unique bundle identifier. This is because the bundle identifier
10 | // is derived based on the 'SAMPLE_CODE_DISAMBIGUATOR' value. Do not use this
11 | // approach in your own projects—it's only useful for sample code projects because
12 | // they are frequently downloaded and don't have a development team set.
13 | SAMPLE_CODE_DISAMBIGUATOR=${DEVELOPMENT_TEAM}
14 |
--------------------------------------------------------------------------------
/docs/images/nfc.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/docs/images/nfc.gif
--------------------------------------------------------------------------------
/docs/images/organization.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/docs/images/organization.gif
--------------------------------------------------------------------------------
/docs/images/overview_after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/docs/images/overview_after.png
--------------------------------------------------------------------------------
/docs/images/overview_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/docs/images/overview_before.png
--------------------------------------------------------------------------------
/docs/images/screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/docs/images/screenshots.png
--------------------------------------------------------------------------------
/docs/images/screenshots_nfc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nativeapptemplate/NativeAppTemplate-Free-iOS/b36b646b27b4bace1960942c79fb4e59edb74da8/docs/images/screenshots_nfc.png
--------------------------------------------------------------------------------