├── Gemfile
├── app-screenshot.png
├── eCommerce
├── Assets.xcassets
│ ├── Contents.json
│ ├── live.imageset
│ │ ├── Image.png
│ │ └── Contents.json
│ ├── home_icon.imageset
│ │ ├── Image.png
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ ├── iOS App Icon_20pt.png
│ │ ├── iOS App Icon_29pt.png
│ │ ├── iOS App Icon_40pt.png
│ │ ├── iOS App Icon_76pt.png
│ │ ├── iOS App Icon_20pt@2x.png
│ │ ├── iOS App Icon_20pt@3x.png
│ │ ├── iOS App Icon_29pt@2x.png
│ │ ├── iOS App Icon_29pt@3x.png
│ │ ├── iOS App Icon_40pt@2x.png
│ │ ├── iOS App Icon_40pt@3x.png
│ │ ├── iOS App Icon_60pt@2x.png
│ │ ├── iOS App Icon_60pt@3x.png
│ │ ├── iOS App Icon_76pt@2x.png
│ │ ├── iOS App Icon_83.5@2x.png
│ │ ├── iOS App Icon_1024pt@1x.png
│ │ ├── iOS App Icon_20pt@2x 1.png
│ │ ├── iOS App Icon_29pt@2x 1.png
│ │ ├── iOS App Icon_40pt@2x 1.png
│ │ └── Contents.json
├── Custom Fonts
│ ├── AmazonEmber-Bold.ttf
│ ├── AmazonEmber-Heavy.ttf
│ ├── AmazonEmber-Medium.ttf
│ └── AmazonEmber-Regular.ttf
├── eCommerce.entitlements
├── Constants.swift
├── AppDelegate.swift
├── Models
│ ├── Products.swift
│ └── Image.swift
├── Views
│ ├── Product
│ │ ├── ProductView.swift
│ │ └── ProductView.xib
│ └── Player
│ │ ├── PlayerView.swift
│ │ └── PlayerView.xib
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
├── Products.json
└── Controllers
│ └── ProductsViewController.swift
├── eCommerce.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ └── eCommerce.xcscheme
└── project.pbxproj
├── eCommerce.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── CODE_OF_CONDUCT.md
├── Podfile.lock
├── Podfile
├── LICENSE
├── .gitignore
├── Gemfile.lock
├── CONTRIBUTING.md
├── README.md
└── THIRD-PARTY-LICENSES.txt
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'cocoapods'
4 |
5 |
--------------------------------------------------------------------------------
/app-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/app-screenshot.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/eCommerce/Custom Fonts/AmazonEmber-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Custom Fonts/AmazonEmber-Bold.ttf
--------------------------------------------------------------------------------
/eCommerce/Custom Fonts/AmazonEmber-Heavy.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Custom Fonts/AmazonEmber-Heavy.ttf
--------------------------------------------------------------------------------
/eCommerce/Custom Fonts/AmazonEmber-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Custom Fonts/AmazonEmber-Medium.ttf
--------------------------------------------------------------------------------
/eCommerce/Custom Fonts/AmazonEmber-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Custom Fonts/AmazonEmber-Regular.ttf
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/live.imageset/Image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/live.imageset/Image.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/home_icon.imageset/Image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/home_icon.imageset/Image.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_76pt.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt@2x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt@3x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt@2x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt@3x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt@2x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt@3x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_60pt@2x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_60pt@3x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_76pt@2x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_83.5@2x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_1024pt@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_1024pt@1x.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt@2x 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_20pt@2x 1.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt@2x 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_29pt@2x 1.png
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt@2x 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/amazon-ivs-ecommerce-ios-demo/HEAD/eCommerce/Assets.xcassets/AppIcon.appiconset/iOS App Icon_40pt@2x 1.png
--------------------------------------------------------------------------------
/eCommerce.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/eCommerce/eCommerce.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.in-app-payments
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/eCommerce.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/eCommerce.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/eCommerce.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AmazonIVSPlayer (1.40.0)
3 |
4 | DEPENDENCIES:
5 | - AmazonIVSPlayer (~> 1.40.0)
6 |
7 | SPEC REPOS:
8 | trunk:
9 | - AmazonIVSPlayer
10 |
11 | SPEC CHECKSUMS:
12 | AmazonIVSPlayer: b1dbf89d0067d016ab734dc6e7ae799ea52101cd
13 |
14 | PODFILE CHECKSUM: d4eedbde72703590cd550010242bace272a68b0e
15 |
16 | COCOAPODS: 1.16.2
17 |
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/live.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Image.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/home_icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Image.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '13.0'
2 |
3 | target 'eCommerce' do
4 | pod 'AmazonIVSPlayer', '~> 1.40.0'
5 | end
6 |
7 | # Allow building for arm64e architecture, which AmazonIVSPlayer supports.
8 | # See https://developer.apple.com/documentation/security/preparing_your_app_to_work_with_pointer_authentication
9 | post_install do |installer|
10 | installer.pods_project.build_configurations.each do |configuration|
11 | configuration.build_settings['ARCHS[sdk=iphoneos*]'] = ['$(ARCHS_STANDARD)','arm64e']
12 | end
13 | end
--------------------------------------------------------------------------------
/eCommerce/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // eCommerce
4 | //
5 | // Created by Uldis Zingis on 01/10/2021.
6 | // Copyright © 2021 Twitch. All rights reserved.
7 |
8 | import Foundation
9 |
10 | enum Constants {
11 | // MARK: Stream url
12 | static let streamUrl = "https://4c62a87c1810.us-west-2.playback.live-video.net/api/video/v1/us-west-2.049054135175.channel.onToXRHIurEP.m3u8"
13 |
14 | // MARK: Product image source url
15 | static let productImageBaseUrl = "https://ecommerce.ivsdemos.com"
16 | }
17 |
--------------------------------------------------------------------------------
/eCommerce/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // eCommerce
4 | //
5 | // Created by Zingis, Uldis on 5/29/20.
6 | // Copyright © 2020 Twitch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AVFoundation
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 |
19 | do {
20 | try AVAudioSession.sharedInstance().setCategory(.playback)
21 | try AVAudioSession.sharedInstance().setActive(true)
22 | } catch {
23 | print("‼️ Could not setup AVAudioSession: \(error)")
24 | }
25 |
26 | return true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/eCommerce/Models/Products.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Product.swift
3 | // eCommerce
4 | //
5 | // Created by Zingis, Uldis on 6/8/20.
6 | // Copyright © 2020 Twitch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct Products: Decodable {
12 | var items: [Product] = []
13 |
14 | enum CodingKeys: String, CodingKey {
15 | case items = "products"
16 | }
17 | }
18 |
19 | struct Product: Decodable, Equatable {
20 | var id: String
21 | var name: String
22 | var imageUrl: String
23 | var imageLargeUrl: String
24 | var price: Int
25 | var discountedPrice: Int
26 | var longDescription: String
27 |
28 | func getImage(completion: @escaping (UIImage?) -> Void) {
29 | if var url = URL(string: Constants.productImageBaseUrl) {
30 | url.appendPathComponent(imageUrl)
31 | Image.getFrom(url) { image in
32 | completion(image)
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/eCommerce/Models/Image.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Image.swift
3 | // eCommerce
4 | //
5 | // Created by Uldis Zingis on 05/10/2021.
6 | // Copyright © 2021 Twitch. All rights reserved.
7 |
8 | import UIKit
9 |
10 | enum Image {
11 | static func getFrom(_ url: URL, completion: @escaping (UIImage?) -> Void) {
12 | URLSession.shared.dataTask(with: url, completionHandler: { (data, _, error) in
13 | guard let data = data, error == nil else {
14 | print("❌ Error getting image from \(url.absoluteString): \(error!)")
15 | DispatchQueue.main.async { completion(nil) }
16 | return
17 | }
18 | if let image = UIImage(data: data) {
19 | DispatchQueue.main.async { completion(image) }
20 | } else {
21 | print("❌ Could not get UIImage from data \(data)")
22 | DispatchQueue.main.async { completion(nil) }
23 | }
24 | }).resume()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | MIT No Attribution
3 |
4 | Copyright 2020 Amazon.com, Inc. or its affiliates.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Build generated
2 | build/
3 | DerivedData/
4 |
5 | ## Various settings
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 |
16 | ## Other
17 | *.moved-aside
18 | *.xccheckout
19 | *.xcscmblueprint
20 |
21 | ## Obj-C/Swift specific
22 | *.hmap
23 | *.ipa
24 | *.dSYM.zip
25 | *.dSYM
26 |
27 | ## Playgrounds
28 | timeline.xctimeline
29 | playground.xcworkspace
30 |
31 | # Swift Package Manager
32 | #
33 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
34 | # Packages/
35 | # Package.pins
36 | # Package.resolved
37 | .build/
38 |
39 | # CocoaPods
40 | #
41 | # We recommend against adding the Pods directory to your .gitignore. However
42 | # you should judge for yourself, the pros and cons are mentioned at:
43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
44 | #
45 | Pods/
46 |
47 | # Carthage
48 | #
49 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
50 | # Carthage/Checkouts
51 |
52 | Carthage/Build
53 |
54 | # fastlane
55 | #
56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
57 | # screenshots whenever they are needed.
58 | # For more information about the recommended setup visit:
59 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
60 |
61 | fastlane/report.xml
62 | fastlane/Preview.html
63 | fastlane/screenshots/**/*.png
64 | fastlane/test_output
65 |
66 | *.DS_Store
67 |
--------------------------------------------------------------------------------
/eCommerce/Views/Product/ProductView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProductView.swift
3 | // eCommerce
4 | //
5 | // Created by Uldis Zingis on 05/10/2021.
6 | // Copyright © 2021 Twitch. All rights reserved.
7 |
8 | import UIKit
9 |
10 | class ProductView: UIView {
11 | @IBOutlet weak var productImageView: UIImageView!
12 | @IBOutlet weak var titleLabel: UILabel!
13 | @IBOutlet weak var priceLabel: UILabel!
14 | @IBOutlet weak var discountLabel: UILabel!
15 | @IBOutlet weak var separatorLine: UIView!
16 |
17 | func setup(with product: Product, in frame: CGRect) {
18 | self.frame = frame
19 |
20 | product.getImage { image in
21 | DispatchQueue.main.async {
22 | self.productImageView.image = image
23 | }
24 | }
25 | productImageView.layer.cornerRadius = 10
26 | titleLabel.text = product.name
27 | if product.discountedPrice != product.price {
28 | let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: "$\(product.price)")
29 | attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 1, range: NSMakeRange(0, attributeString.length))
30 | priceLabel.attributedText = attributeString
31 | } else {
32 | priceLabel.text = "$\(product.price)"
33 | priceLabel.textColor = .white
34 | }
35 |
36 | discountLabel.text = "$\(product.discountedPrice)"
37 | discountLabel.isHidden = product.discountedPrice == product.price
38 |
39 | setNeedsLayout()
40 | layoutIfNeeded()
41 | }
42 |
43 | func showBottomSeparator(_ isVisible: Bool) {
44 | separatorLine.isHidden = !isVisible
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/eCommerce/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/eCommerce/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIAppFonts
24 |
25 | AmazonEmber-Bold.ttf
26 | AmazonEmber-Heavy.ttf
27 | AmazonEmber-Medium.ttf
28 | AmazonEmber-Regular.ttf
29 |
30 | UILaunchStoryboardName
31 | LaunchScreen
32 | UIMainStoryboardFile
33 | Main
34 | UIRequiredDeviceCapabilities
35 |
36 | armv7
37 |
38 | UIStatusBarStyle
39 | UIStatusBarStyleLightContent
40 | UISupportedInterfaceOrientations
41 |
42 | UIInterfaceOrientationPortrait
43 |
44 | UISupportedInterfaceOrientations~ipad
45 |
46 | UIInterfaceOrientationPortrait
47 | UIInterfaceOrientationPortraitUpsideDown
48 | UIInterfaceOrientationLandscapeLeft
49 | UIInterfaceOrientationLandscapeRight
50 |
51 | UIViewControllerBasedStatusBarAppearance
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.2)
5 | activesupport (4.2.11.3)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | algoliasearch (1.27.4)
11 | httpclient (~> 2.8, >= 2.8.3)
12 | json (>= 1.5.1)
13 | atomos (0.1.3)
14 | claide (1.0.3)
15 | cocoapods (1.9.3)
16 | activesupport (>= 4.0.2, < 5)
17 | claide (>= 1.0.2, < 2.0)
18 | cocoapods-core (= 1.9.3)
19 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
20 | cocoapods-downloader (>= 1.2.2, < 2.0)
21 | cocoapods-plugins (>= 1.0.0, < 2.0)
22 | cocoapods-search (>= 1.0.0, < 2.0)
23 | cocoapods-stats (>= 1.0.0, < 2.0)
24 | cocoapods-trunk (>= 1.4.0, < 2.0)
25 | cocoapods-try (>= 1.1.0, < 2.0)
26 | colored2 (~> 3.1)
27 | escape (~> 0.0.4)
28 | fourflusher (>= 2.3.0, < 3.0)
29 | gh_inspector (~> 1.0)
30 | molinillo (~> 0.6.6)
31 | nap (~> 1.0)
32 | ruby-macho (~> 1.4)
33 | xcodeproj (>= 1.14.0, < 2.0)
34 | cocoapods-core (1.9.3)
35 | activesupport (>= 4.0.2, < 6)
36 | algoliasearch (~> 1.0)
37 | concurrent-ruby (~> 1.1)
38 | fuzzy_match (~> 2.0.4)
39 | nap (~> 1.0)
40 | netrc (~> 0.11)
41 | typhoeus (~> 1.0)
42 | cocoapods-deintegrate (1.0.4)
43 | cocoapods-downloader (1.6.3)
44 | cocoapods-plugins (1.0.0)
45 | nap
46 | cocoapods-search (1.0.0)
47 | cocoapods-stats (1.1.0)
48 | cocoapods-trunk (1.5.0)
49 | nap (>= 0.8, < 2.0)
50 | netrc (~> 0.11)
51 | cocoapods-try (1.2.0)
52 | colored2 (3.1.2)
53 | concurrent-ruby (1.1.7)
54 | escape (0.0.4)
55 | ethon (0.12.0)
56 | ffi (>= 1.3.0)
57 | ffi (1.13.1)
58 | fourflusher (2.3.1)
59 | fuzzy_match (2.0.4)
60 | gh_inspector (1.1.3)
61 | httpclient (2.8.3)
62 | i18n (0.9.5)
63 | concurrent-ruby (~> 1.0)
64 | json (2.3.1)
65 | minitest (5.14.2)
66 | molinillo (0.6.6)
67 | nanaimo (0.3.0)
68 | nap (1.1.0)
69 | netrc (0.11.0)
70 | ruby-macho (1.4.0)
71 | thread_safe (0.3.6)
72 | typhoeus (1.4.0)
73 | ethon (>= 0.9.0)
74 | tzinfo (1.2.10)
75 | thread_safe (~> 0.1)
76 | xcodeproj (1.18.0)
77 | CFPropertyList (>= 2.3.3, < 4.0)
78 | atomos (~> 0.1.3)
79 | claide (>= 1.0.2, < 2.0)
80 | colored2 (~> 3.1)
81 | nanaimo (~> 0.3.0)
82 |
83 | PLATFORMS
84 | ruby
85 |
86 | DEPENDENCIES
87 | cocoapods
88 |
89 | BUNDLED WITH
90 | 2.1.4
91 |
--------------------------------------------------------------------------------
/eCommerce/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "iOS App Icon_20pt@2x 1.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "iOS App Icon_20pt@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "iOS App Icon_29pt@2x 1.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "iOS App Icon_29pt@3x.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "iOS App Icon_40pt@2x 1.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "iOS App Icon_40pt@3x.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "iOS App Icon_60pt@2x.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "iOS App Icon_60pt@3x.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "iOS App Icon_20pt.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "iOS App Icon_20pt@2x.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "iOS App Icon_29pt.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "iOS App Icon_29pt@2x.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "iOS App Icon_40pt.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "iOS App Icon_40pt@2x.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "iOS App Icon_76pt.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "iOS App Icon_76pt@2x.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "iOS App Icon_83.5@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "iOS App Icon_1024pt@1x.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | }
111 | ],
112 | "info" : {
113 | "author" : "xcode",
114 | "version" : 1
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/eCommerce.xcodeproj/xcshareddata/xcschemes/eCommerce.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/eCommerce/Products.json:
--------------------------------------------------------------------------------
1 | {
2 | "products": [
3 | {
4 | "id": "1000567890",
5 | "name": "Eyeshadow Palette - Set of 3 Palettes",
6 | "imageUrl": "images/1000567890.jpg",
7 | "imageLargeUrl": "images/1000567890-lg.jpg",
8 | "price": 125,
9 | "discountedPrice": 99,
10 | "longDescription": "By combining potent natural ingredients, ancient rituals, and modern science, ACME creates products that perform with experiences that transform. Discovery drives innovation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vulputate volutpat rhoncus arcu porttitor in."
11 | }, {
12 | "id": "1000567891",
13 | "name": "Waterproof Eyeliner and Mascara",
14 | "imageUrl": "images/1000567891.jpg",
15 | "imageLargeUrl": "images/1000567891-lg.jpg",
16 | "price": 27,
17 | "discountedPrice": 27,
18 | "longDescription": "By combining potent natural ingredients, ancient rituals, and modern science, ACME creates products that perform with experiences that transform. Discovery drives innovation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vulputate volutpat rhoncus arcu porttitor in."
19 | }, {
20 | "id": "1000567892",
21 | "name": "Divine Shine - Satin Rose Lipstick set",
22 | "imageUrl": "images/1000567892.jpg",
23 | "imageLargeUrl": "images/1000567892-lg.jpg",
24 | "price": 35,
25 | "discountedPrice": 28,
26 | "longDescription": "By combining potent natural ingredients, ancient rituals, and modern science, ACME creates products that perform with experiences that transform. Discovery drives innovation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vulputate volutpat rhoncus arcu porttitor in."
27 | }, {
28 | "id": "1000567893",
29 | "name": "Gloss Bomb Universal Lip Luminizer",
30 | "imageUrl": "images/1000567893.jpg",
31 | "imageLargeUrl": "images/1000567893-lg.jpg",
32 | "price": 19,
33 | "discountedPrice": 15,
34 | "longDescription": "By combining potent natural ingredients, ancient rituals, and modern science, ACME creates products that perform with experiences that transform. Discovery drives innovation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vulputate volutpat rhoncus arcu porttitor in."
35 | }, {
36 | "id": "1000567894",
37 | "name": "7-in-1 daily wear palette essentials",
38 | "imageUrl": "images/1000567894.jpg",
39 | "imageLargeUrl": "images/1000567894-lg.jpg",
40 | "price": 103,
41 | "discountedPrice": 95,
42 | "longDescription": "By combining potent natural ingredients, ancient rituals, and modern science, ACME creates products that perform with experiences that transform. Discovery drives innovation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vulputate volutpat rhoncus arcu porttitor in."
43 | }, {
44 | "id": "1000567895",
45 | "name": "EYE CARE SET - sparkle gloss eyes and lashes",
46 | "imageUrl": "images/1000567895.jpg",
47 | "imageLargeUrl": "images/1000567895-lg.jpg",
48 | "price": 59,
49 | "discountedPrice": 45,
50 | "longDescription": "By combining potent natural ingredients, ancient rituals, and modern science, ACME creates products that perform with experiences that transform. Discovery drives innovation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vulputate volutpat rhoncus arcu porttitor in."
51 | }
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Amazon IVS eCommerce iOS Demo
2 |
3 | A demo iOS application intended as an educational tool for demonstrating how Amazon IVS can be used to build a compelling customer experience for eCommerce use-cases.
4 |
5 |
6 |
7 | **This project is intended for education purposes only and not for production usage.**
8 |
9 | This is an iOS application that uses [Amazon IVS TimedMetadata](https://docs.aws.amazon.com/ivs/latest/userguide/SEM.html) to show products. The demo is written in [Swift](https://developer.apple.com/swift/) and showcases how customers can load and play an Amazon IVS stream and display browsable product information using TimedMetadata.
10 |
11 | This demo uses a 24/7 [looping stream](https://4c62a87c1810.us-west-2.playback.live-video.net/api/video/v1/us-west-2.049054135175.channel.onToXRHIurEP.m3u8) which emits a TimedMetadata event every few seconds. These TimedMetadata events describe product information in json format, which is used by the app to show a carousel of products, and to highlight the product being shown on stream.
12 |
13 | ## Getting Started
14 |
15 | To run this demo, you will need the following:
16 |
17 | 1. [Xcode](https://apps.apple.com/us/app/xcode/id497799835) 11 or greater. You can build this application for devices running iOS 10 or later.
18 | 2. [Cocoapods](https://cocoapods.org/), installed and up-to-date. You can quickly install cocoapods from Terminal with `sudo gem install cocoapods`.
19 | 3. [Bundler](https://bundler.io/), installed and up-to-date.
20 | 4. [Git](https://git-scm.com/) installed and up-to-date. You can quickly install git from Terminal with `xcode-select --install`.
21 |
22 | To run the demo in the iOS Simulator:
23 |
24 | 1. Clone the project from this repository
25 | 2. Navigate to the project directory using _Terminal_
26 | 3. Run `pod install`
27 | 4. Open `eCommerce.xcworkspace` in Xcode.
28 | 5. Click `Run` in the toolbar, or press `Cmd(⌘)-R`
29 |
30 | You should see the iOS Simulator boot up and launch the demo app. This may take a few moments to complete.
31 |
32 | ## Modifying this Example
33 |
34 | ### Prerequisites
35 |
36 | **IMPORTANT NOTE:** Using your own stream will create and consume AWS resources, which will cost money.
37 |
38 | 1. Create and set up an Amazon IVS channel. [Getting started with Amazon IVS](https://docs.aws.amazon.com/ivs/latest/userguide/GSIVS.html).
39 |
40 | ### Using your own Live video
41 |
42 | 1. Open the [Amazon IVS Console](https://console.aws.amazon.com/ivs) and navigate to the channel you would like to use.
43 | 2. Copy the _Playback URL_ for the channel. The URL should end in `.m3u8`. (For example: `https://4c62a87c1810.us-west-2.playback.live-video.net/api/video/v1/us-west-2.049054135175.channel.onToXRHIurEP.m3u8`).
44 | 3. In Xcode, open `eCommerce/Constants.swift`.
45 | 4. Replace the string on line `12` with the _Playback URL_ from step 2.
46 | 5. Save and build the application. Navigate to the leftmost image in the `LIVE` carousel to view landscape video, or the second-to-leftmost image to view portrait video.
47 |
48 | ### Using your own TimedMetadata events
49 |
50 | Amazon IVS TimedMetadata provides a way to embed metadata in an Amazon IVS stream. It ensures that your users receive the metadata at the same time as the video stream, regardless of stream latency or geographic location. Learn how to embed TimedMetadata in stream: [Embedding Metadata within a Video Stream](https://docs.aws.amazon.com/ivs/latest/userguide/SEM.html).
51 |
52 | This example expects a `productId` that represents the unique identifier for a project in the `streams.json` file.
53 |
54 | ```
55 | "metadata" : {
56 | "productId": "1000567892"
57 | }
58 | ```
59 |
60 | ## Additional Notes
61 |
62 | For production applications, we recommend using TimedMetadata alongside services like [AWS Lambda](https://aws.amazon.com/lambda/), [Amazon API Gateway](https://aws.amazon.com/api-gateway/), and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). These services will let you store and retrieve product information in a more scalable way. See the [Amazon IVS eCommerce Web Demo](https://github.com/aws-samples/amazon-ivs-ecommerce-web-demo) for example code using these services.
63 |
64 | ## Documentation
65 |
66 | - [Amazon IVS Amazon Interactive Video Service (Amazon IVS)](https://aws.amazon.com/ivs/) is a managed live streaming solution that is quick and easy to set up, and ideal for creating interactive video experiences. Simply send your live streams to Amazon IVS and the service does everything you need to make ultra-low latency live video available to any viewer around the world, letting you focus on building interactive experiences alongside the live video. [Learn more](https://aws.amazon.com/ivs/).
67 | - [Amazon IVS docs](https://docs.aws.amazon.com/ivs/)
68 | - [User Guide](https://docs.aws.amazon.com/ivs/latest/userguide/)
69 | - [API Reference](https://docs.aws.amazon.com/ivs/latest/APIReference/)
70 |
71 | ## Known Issues
72 |
73 | - The application was written for demonstration purposes and not for production use.
74 | - Currently only tested in the us-west-2 (Oregon) region. Additional regions may be supported depending on service availability.
75 |
76 | ## License
77 |
78 | This sample code is made available under a modified MIT license. See the LICENSE file.
79 |
--------------------------------------------------------------------------------
/eCommerce/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/THIRD-PARTY-LICENSES.txt:
--------------------------------------------------------------------------------
1 | **The following images are derivatives of "flowers Icon #1382329" by Made, used under CC BY 3.0 (available at https://creativecommons.org/licenses/by/3.0/us/legalcode).
2 |
3 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-1024@1x.png
4 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-83.5@2x.png
5 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-76@2x.png
6 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-60@3x.png
7 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-60@2x.png
8 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-40@3x.png
9 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-40@2x.png
10 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-29@3x.png
11 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-29@2x.png
12 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-20@3x.png
13 | eCommerce/Assets.xcassets/AppIcon.appiconset/icon-ios-20@2x.png
14 |
15 |
16 | **The following images are used, or derivatives of images used, under the Pexels License (available at https://www.pexels.com/terms-of-service/):
17 |
18 | eCommerce/Assets.xcassets/avatar_1.imageset
19 | eCommerce/Assets.xcassets/avatar_2.imageset
20 | eCommerce/Assets.xcassets/avatar_3.imageset
21 | eCommerce/Assets.xcassets/avatar_4.imageset
22 | eCommerce/Assets.xcassets/daily_offer_1.imageset
23 | eCommerce/Assets.xcassets/daily_offer_2.imageset
24 | eCommerce/Assets.xcassets/daily_offer_3.imageset
25 |
26 |
27 | **The following fonts are used and licensed under the SIL Open Font License, Version 1.1.
28 | This license is copied below, and is also available with a FAQ at:
29 | http://scripts.sil.org/OFL
30 |
31 | eCommerce/Custom Fonts/DMSans-Bold.ttf
32 | eCommerce/Custom Fonts/DMSans-Medium.ttf
33 | eCommerce/Custom Fonts/DMSans-Regular.ttf
34 | eCommerce/Custom Fonts/DMSerifDisplay-Regular.ttf
35 |
36 | -----------------------------------------------------------
37 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
38 | -----------------------------------------------------------
39 |
40 | PREAMBLE
41 | The goals of the Open Font License (OFL) are to stimulate worldwide
42 | development of collaborative font projects, to support the font creation
43 | efforts of academic and linguistic communities, and to provide a free and
44 | open framework in which fonts may be shared and improved in partnership
45 | with others.
46 |
47 | The OFL allows the licensed fonts to be used, studied, modified and
48 | redistributed freely as long as they are not sold by themselves. The
49 | fonts, including any derivative works, can be bundled, embedded,
50 | redistributed and/or sold with any software provided that any reserved
51 | names are not used by derivative works. The fonts and derivatives,
52 | however, cannot be released under any other type of license. The
53 | requirement for fonts to remain under this license does not apply
54 | to any document created using the fonts or their derivatives.
55 |
56 | DEFINITIONS
57 | "Font Software" refers to the set of files released by the Copyright
58 | Holder(s) under this license and clearly marked as such. This may
59 | include source files, build scripts and documentation.
60 |
61 | "Reserved Font Name" refers to any names specified as such after the
62 | copyright statement(s).
63 |
64 | "Original Version" refers to the collection of Font Software components as
65 | distributed by the Copyright Holder(s).
66 |
67 | "Modified Version" refers to any derivative made by adding to, deleting,
68 | or substituting -- in part or in whole -- any of the components of the
69 | Original Version, by changing formats or by porting the Font Software to a
70 | new environment.
71 |
72 | "Author" refers to any designer, engineer, programmer, technical
73 | writer or other person who contributed to the Font Software.
74 |
75 | PERMISSION & CONDITIONS
76 | Permission is hereby granted, free of charge, to any person obtaining
77 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
78 | redistribute, and sell modified and unmodified copies of the Font
79 | Software, subject to the following conditions:
80 |
81 | 1) Neither the Font Software nor any of its individual components,
82 | in Original or Modified Versions, may be sold by itself.
83 |
84 | 2) Original or Modified Versions of the Font Software may be bundled,
85 | redistributed and/or sold with any software, provided that each copy
86 | contains the above copyright notice and this license. These can be
87 | included either as stand-alone text files, human-readable headers or
88 | in the appropriate machine-readable metadata fields within text or
89 | binary files as long as those fields can be easily viewed by the user.
90 |
91 | 3) No Modified Version of the Font Software may use the Reserved Font
92 | Name(s) unless explicit written permission is granted by the corresponding
93 | Copyright Holder. This restriction only applies to the primary font name as
94 | presented to the users.
95 |
96 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
97 | Software shall not be used to promote, endorse or advertise any
98 | Modified Version, except to acknowledge the contribution(s) of the
99 | Copyright Holder(s) and the Author(s) or with their explicit written
100 | permission.
101 |
102 | 5) The Font Software, modified or unmodified, in part or in whole,
103 | must be distributed entirely under this license, and must not be
104 | distributed under any other license. The requirement for fonts to
105 | remain under this license does not apply to any document created
106 | using the Font Software.
107 |
108 | TERMINATION
109 | This license becomes null and void if any of the above conditions are
110 | not met.
111 |
112 | DISCLAIMER
113 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
114 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
115 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
116 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
117 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
118 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
119 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
120 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
121 | OTHER DEALINGS IN THE FONT SOFTWARE.
122 |
--------------------------------------------------------------------------------
/eCommerce/Controllers/ProductsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // eCommerce
4 | //
5 | // Created by Zingis, Uldis on 5/29/20.
6 | // Copyright © 2020 Twitch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AmazonIVSPlayer
11 |
12 | class ProductsViewController: UIViewController {
13 |
14 | // MARK: IBOutlet
15 | @IBOutlet weak var tableView: UITableView!
16 |
17 | private var playerView: PlayerView?
18 | private var headerTitleLabel: UILabel?
19 |
20 | // MARK: View Lifecycle
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | tableView.delegate = self
26 | tableView.dataSource = self
27 | tableView.layer.cornerRadius = 30
28 |
29 | loadProducts()
30 | }
31 |
32 | override func viewDidAppear(_ animated: Bool) {
33 | super.viewDidAppear(animated)
34 | createPlayerView()
35 | playerView?.startPlayback()
36 | playerView?.addApplicationLifecycleObservers()
37 | }
38 |
39 | override func viewDidDisappear(_ animated: Bool) {
40 | super.viewDidDisappear(animated)
41 | playerView?.pausePlayback()
42 | playerView?.removeApplicationLifecycleObservers()
43 | }
44 |
45 | // MARK: Custom actions
46 |
47 | private var products: [Product] = []
48 |
49 | private func loadProducts() {
50 | if let path = Bundle.main.path(forResource: "Products", ofType: "json") {
51 | do {
52 | let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
53 | do {
54 | self.products = try JSONDecoder().decode(Products.self, from: data).items
55 | } catch {
56 | print("‼️ Error decoding products: \(error)")
57 | }
58 | } catch {
59 | print("‼️ Error: \(error)")
60 | }
61 | }
62 | }
63 |
64 | private func createPlayerView() {
65 | playerView = Bundle.main.loadNibNamed("PlayerView", owner: self, options: nil)?[0] as? PlayerView
66 | playerView?.collapsedSize = CGRect(x: 0, y: 0, width: 100, height: 205)
67 | playerView?.frame = CGRect(x: 0, y: 0, width: 100, height: 205)
68 | playerView?.expandedSize = tableView.frame
69 | playerView?.collapsedCenterPosition = CGPoint(
70 | x: tableView.frame.width - (playerView?.frame.width ?? 0) * 0.6,
71 | y: tableView.frame.height - (playerView?.frame.height ?? 0) * 0.5
72 | )
73 |
74 | if let playerView = playerView {
75 | view.addSubview(playerView)
76 | view.bringSubviewToFront(playerView)
77 | playerView.setup()
78 | playerView.setNeedsLayout()
79 | }
80 |
81 | playerView?.state = .expanded
82 | playerView?.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGestureHandler)))
83 | playerView?.products = products
84 |
85 | UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut) {
86 | self.view.layoutSubviews()
87 | }
88 | }
89 |
90 | @objc private func panGestureHandler(gesture: UIPanGestureRecognizer) {
91 | guard let playerView = playerView, playerView.state == .collapsed else {
92 | return
93 | }
94 | let newLocation = gesture.location(in: view)
95 | playerView.center = newLocation
96 |
97 | if gesture.state == .ended {
98 | let isCloserToViewTop = playerView.frame.midY <= self.view.frame.height / 2
99 |
100 | if playerView.frame.midX >= self.view.frame.width / 2 {
101 | UIView.animate(
102 | withDuration: 0.5,
103 | delay: 0,
104 | usingSpringWithDamping: 1,
105 | initialSpringVelocity: 1,
106 | options: .curveEaseIn,
107 | animations: {
108 | playerView.center.x = self.tableView.frame.width - playerView.frame.width * 0.6
109 | playerView.center.y = isCloserToViewTop ?
110 | playerView.frame.height * 0.8 :
111 | self.tableView.frame.height - playerView.frame.height * 0.5
112 | },
113 | completion: {_ in
114 | playerView.collapsedCenterPosition = playerView.center
115 | }
116 | )
117 | } else {
118 | UIView.animate(
119 | withDuration: 0.5,
120 | delay: 0,
121 | usingSpringWithDamping: 1,
122 | initialSpringVelocity: 1,
123 | options: .curveEaseIn,
124 | animations: {
125 | playerView.center.x = playerView.frame.width * 0.6
126 | playerView.center.y = isCloserToViewTop ?
127 | playerView.frame.height * 0.8 :
128 | self.tableView.frame.height - playerView.frame.height * 0.5
129 | },
130 | completion: {_ in
131 | playerView.collapsedCenterPosition = playerView.center
132 | }
133 | )
134 | }
135 | }
136 | }
137 | }
138 |
139 | // MARK: UITableViewDelegate
140 |
141 | extension ProductsViewController: UITableViewDelegate {
142 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
143 | let headerView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tableView.frame.width, height: 120))
144 | let title = UILabel()
145 | title.frame = CGRect.init(x: 16, y: 0, width: headerView.frame.width, height: headerView.frame.height)
146 | title.textColor = .white
147 | title.font = UIFont(name: "AmazonEmber-Bold", size: 24)
148 | title.text = "All Products"
149 | headerTitleLabel = title
150 | headerView.addSubview(title)
151 | return headerView
152 | }
153 |
154 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
155 | return 120
156 | }
157 |
158 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
159 | let offset = scrollView.contentOffset.y
160 | var alpha: CGFloat = 1
161 | if offset > 70 {
162 | alpha = (100 - offset) / 100
163 | }
164 | headerTitleLabel?.textColor = UIColor(red: 1, green: 1, blue: 1, alpha: alpha)
165 | }
166 | }
167 |
168 | // MARK: UITableViewDataSource
169 |
170 | extension ProductsViewController: UITableViewDataSource {
171 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
172 | return products.count
173 | }
174 |
175 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
176 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell") else {
177 | return UITableViewCell()
178 | }
179 |
180 | if let productView = Bundle.main.loadNibNamed("ProductView", owner: self, options: nil)?[0] as? ProductView {
181 | productView.setup(with: products[indexPath.row], in: cell.bounds)
182 | productView.showBottomSeparator(indexPath.row != products.count - 1)
183 | cell.addSubview(productView)
184 | cell.layoutSubviews()
185 | }
186 |
187 | return cell
188 | }
189 | }
190 |
191 | // MARK: PlayerViewDelegate
192 |
193 | extension ProductsViewController: PlayerViewDelegate {
194 | func show(_ alert: UIAlertController, animated: Bool) {
195 | DispatchQueue.main.async {
196 | self.present(alert, animated: animated)
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/eCommerce/Views/Product/ProductView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | AmazonEmber-Medium
13 |
14 |
15 | AmazonEmber-Regular
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/eCommerce/Views/Player/PlayerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlayerView.swift
3 | // eCommerce
4 | //
5 | // Created by Uldis Zingis on 04/10/2021.
6 | // Copyright © 2021 Twitch. All rights reserved.
7 |
8 | import UIKit
9 | import AmazonIVSPlayer
10 |
11 | enum PlayerViewState {
12 | case collapsed, expanded
13 | }
14 |
15 | protocol PlayerViewDelegate {
16 | func show(_ alert: UIAlertController, animated: Bool)
17 | }
18 |
19 | class PlayerView: UIView {
20 | var delegate: PlayerViewDelegate?
21 | var collapsedCenterPosition = CGPoint(x: 0, y: 0)
22 | var collapsedSize = CGRect(x: 0, y: 0, width: 120, height: 200)
23 | var expandedSize = UIScreen.main.bounds
24 | var products: [Product] = []
25 |
26 | private let jsonDecoder = JSONDecoder()
27 | private var ivsView: IVSPlayerView?
28 | private var currentProduct: Product?
29 | private var controlsViewCollapsed: Bool = false
30 | private var timer: Timer?
31 | private var currentSeconds: Int = 11
32 | private var receivedProductsLine: [Product] = [] {
33 | didSet {
34 | if oldValue.count == 0 && receivedProductsLine.count == 1 {
35 | // Start showing the products
36 | showNextProductInLine()
37 | }
38 | }
39 | }
40 |
41 | // MARK: - IBOutlet
42 |
43 | @IBOutlet weak var controlsView: UIView! {
44 | didSet {
45 | controlsView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(controlsViewTapped)))
46 | }
47 | }
48 | @IBOutlet weak var homeButton: UIButton!
49 | @IBOutlet weak var bufferIndicator: UIActivityIndicatorView!
50 | @IBOutlet weak var streamInfoPill: UIView!
51 | @IBOutlet weak var streamInfoPillImageView: UIImageView!
52 | @IBOutlet weak var productPopup: UIView!
53 | @IBOutlet weak var productAddToCartButton: UIButton!
54 | @IBOutlet weak var productBuyNowButton: UIButton!
55 | @IBOutlet weak var productHolderView: UIView!
56 | @IBOutlet weak var bottomGradientView: UIView!
57 | @IBOutlet weak var timerView: UIView!
58 | @IBOutlet weak var timerLabel: UILabel!
59 | @IBOutlet weak var productsPopupBottomConstraint: NSLayoutConstraint!
60 | @IBOutlet weak var homeButtonTopConstraint: NSLayoutConstraint!
61 | @IBOutlet weak var streamInfoPillTopConstraint: NSLayoutConstraint!
62 |
63 | var state: PlayerViewState? {
64 | didSet {
65 | setSizeForState()
66 | }
67 | }
68 |
69 | // MARK: View Lifecycle
70 |
71 | override init(frame: CGRect) {
72 | super.init(frame: frame)
73 | commonInit()
74 | }
75 |
76 | required init?(coder aDecoder: NSCoder) {
77 | super.init(coder: aDecoder)
78 | commonInit()
79 | }
80 |
81 | func commonInit() {
82 | ivsView = IVSPlayerView(frame: expandedSize)
83 | guard let ivsView = ivsView else {
84 | return
85 | }
86 | ivsView.backgroundColor = .black
87 | ivsView.layer.masksToBounds = true
88 | ivsView.clipsToBounds = true
89 | ivsView.videoGravity = AVLayerVideoGravity.resizeAspectFill
90 | ivsView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(collapsedPlayerTapped)))
91 | self.addSubview(ivsView)
92 |
93 | if let url = URL(string: Constants.streamUrl) {
94 | loadStream(from: url)
95 | startPlayback()
96 | }
97 | }
98 |
99 | deinit {
100 | timer?.invalidate()
101 | }
102 |
103 | func addApplicationLifecycleObservers() {
104 | NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(notification:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
105 | NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil)
106 | }
107 |
108 | func removeApplicationLifecycleObservers() {
109 | NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
110 | NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
111 | }
112 |
113 | @objc private func applicationDidEnterBackground(notification: Notification) {
114 | if player?.state == .playing || player?.state == .buffering {
115 | pausePlayback()
116 | }
117 | }
118 |
119 | @objc private func applicationWillEnterForeground(notification: Notification) {
120 | startPlayback()
121 | }
122 |
123 | func setup() {
124 | streamInfoPill.backgroundColor = .white
125 | streamInfoPill.layer.cornerRadius = 25
126 | streamInfoPillImageView.layer.cornerRadius = streamInfoPillImageView.frame.size.width / 2
127 | if let imageUrl = URL(string: "https://ecommerce.ivsdemos.com/images/profile.png") {
128 | Image.getFrom(imageUrl) { [weak self] (image) in
129 | DispatchQueue.main.async {
130 | self?.streamInfoPillImageView.image = image
131 | }
132 | }
133 | }
134 | productAddToCartButton.layer.cornerRadius = 4
135 | productBuyNowButton.layer.cornerRadius = 4
136 | productPopup.layer.cornerRadius = 16
137 | timerView.layer.cornerRadius = 16
138 | timerView.isHidden = true
139 | homeButton.layer.cornerRadius = homeButton.layer.bounds.width / 2
140 | homeButton.titleLabel?.text = ""
141 | bringSubviewToFront(controlsView)
142 |
143 | let gradient = CAGradientLayer()
144 | gradient.colors = [
145 | UIColor(red: 0, green: 0, blue: 0, alpha: 0).cgColor,
146 | UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor
147 | ]
148 | gradient.locations = [0.1, 1.0]
149 | gradient.frame = bottomGradientView.bounds
150 | bottomGradientView.layer.insertSublayer(gradient, at: 0)
151 | }
152 |
153 | // MARK: Custom actions
154 |
155 | private func setSizeForState() {
156 | switch state {
157 | case .collapsed:
158 | self.frame = collapsedSize
159 | self.layer.cornerRadius = 10
160 | ivsView?.frame = self.bounds
161 | ivsView?.layer.cornerRadius = 10
162 | self.center = collapsedCenterPosition
163 | toggleControlsView(true)
164 |
165 | case .expanded:
166 | self.frame = expandedSize
167 | self.layer.cornerRadius = 30
168 | ivsView?.frame = self.bounds
169 | ivsView?.layer.cornerRadius = 30
170 | toggleControlsView(false)
171 |
172 | case .none:
173 | break
174 | }
175 |
176 | setNeedsLayout()
177 | layoutIfNeeded()
178 | }
179 |
180 | private func toggleControlsView(_ hide: Bool? = nil) {
181 | if let hide = hide {
182 | controlsView.isHidden = hide
183 | } else {
184 | controlsView.isHidden.toggle()
185 | }
186 | }
187 |
188 | private func showNextProductInLine() {
189 | if let nextProductInLine = receivedProductsLine.first {
190 | show(nextProductInLine)
191 | if !receivedProductsLine.isEmpty {
192 | receivedProductsLine.remove(at: 0)
193 | }
194 | }
195 | }
196 |
197 | private func show(_ product: Product) {
198 | guard state != .collapsed, currentProduct == nil else {
199 | return
200 | }
201 |
202 | self.layoutIfNeeded()
203 | if productPopup.isHidden {
204 | if let productView = Bundle.main.loadNibNamed("ProductView", owner: self, options: nil)?[0] as? ProductView {
205 | productView.setup(with: product, in: productHolderView.bounds)
206 | productHolderView.addSubview(productView)
207 | productHolderView.layoutSubviews()
208 | }
209 | self.productPopup.isHidden = false
210 | self.timerView.isHidden = self.controlsViewCollapsed
211 |
212 | UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
213 | self.productsPopupBottomConstraint.constant = self.controlsViewCollapsed ? -30 : 50
214 | self.layoutIfNeeded()
215 | } completion: { _ in
216 | self.startCountdown()
217 | }
218 | currentProduct = product
219 | if !receivedProductsLine.isEmpty {
220 | receivedProductsLine.remove(at: 0)
221 | }
222 | } else {
223 | UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) {
224 | self.productsPopupBottomConstraint.constant = -self.productPopup.frame.height
225 | self.layoutIfNeeded()
226 | } completion: { _ in
227 | self.currentProduct = nil
228 | self.productHolderView.subviews[0].removeFromSuperview()
229 | self.productPopup.isHidden = true
230 | self.timerView.isHidden = true
231 | self.show(product)
232 | }
233 | }
234 | }
235 |
236 | private func startCountdown() {
237 | if let timer = timer {
238 | timer.invalidate()
239 | }
240 | currentSeconds = 11
241 | timerUpdated()
242 | timer = Timer.scheduledTimer(timeInterval: 1,
243 | target: self,
244 | selector: #selector(timerUpdated),
245 | userInfo: nil,
246 | repeats: true)
247 | }
248 |
249 | @objc private func timerUpdated() {
250 | currentSeconds -= 1
251 | timerLabel.text = "0:\(currentSeconds < 10 ? "0" : "")\(currentSeconds)"
252 | if currentSeconds == 0 {
253 | timerView.isHidden = true
254 | currentProduct = nil
255 | timer?.invalidate()
256 | showNextProductInLine()
257 | }
258 | }
259 |
260 | // MARK: - Player
261 |
262 | var player: IVSPlayer? {
263 | didSet {
264 | if oldValue != nil {
265 | removeApplicationLifecycleObservers()
266 | }
267 | ivsView?.player = player
268 | if player != nil {
269 | addApplicationLifecycleObservers()
270 | }
271 | }
272 | }
273 |
274 | // MARK: Playback Control
275 |
276 | private func loadStream(from streamURL: URL) {
277 | let player: IVSPlayer
278 | if let existingPlayer = self.player {
279 | player = existingPlayer
280 | } else {
281 | player = IVSPlayer()
282 | player.delegate = self
283 | self.player = player
284 | print("ℹ️ Player initialized: version \(player.version)")
285 | }
286 | player.load(streamURL)
287 | }
288 |
289 | func startPlayback() {
290 | player?.play()
291 | }
292 |
293 | func pausePlayback() {
294 | player?.pause()
295 | }
296 |
297 | // MARK: - IBAction
298 |
299 | @IBAction func didTapHomeButton(_ sender: Any) {
300 | state = .collapsed
301 | }
302 |
303 | @objc private func collapsedPlayerTapped() {
304 | if state == .collapsed {
305 | state = .expanded
306 | showNextProductInLine()
307 | // self.timerView.isHidden = false
308 | } else {
309 | controlsViewTapped()
310 | }
311 | }
312 |
313 | @objc private func controlsViewTapped() {
314 | controlsViewCollapsed.toggle()
315 | UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
316 | self.streamInfoPillTopConstraint.constant = self.controlsViewCollapsed ? -100 : 8
317 | self.homeButtonTopConstraint.constant = self.controlsViewCollapsed ? -100 : 8
318 | self.productsPopupBottomConstraint.constant = self.controlsViewCollapsed ? -30 : 50
319 | self.timerView.isHidden = self.currentProduct == nil || self.controlsViewCollapsed
320 | self.bottomGradientView.isHidden = self.currentProduct == nil || !self.controlsViewCollapsed
321 | self.layoutIfNeeded()
322 | }
323 | }
324 |
325 |
326 | // MARK: State display
327 |
328 | private func presentError(_ error: Error, componentName: String) {
329 | let alert = UIAlertController(title: "\(componentName) Error", message: String(reflecting: error), preferredStyle: .alert)
330 | alert.addAction(UIAlertAction(title: "Close", style: .cancel))
331 | delegate?.show(alert, animated: true)
332 | }
333 |
334 | private func presentAlert(_ message: String, componentName: String) {
335 | let alert = UIAlertController(title: "\(componentName)", message: message, preferredStyle: .alert)
336 | alert.addAction(UIAlertAction(title: "Close", style: .cancel))
337 | delegate?.show(alert, animated: true)
338 | }
339 |
340 | private func updateForState(_ state: IVSPlayer.State) {
341 | if state == .buffering {
342 | bufferIndicator?.startAnimating()
343 | } else {
344 | bufferIndicator?.stopAnimating()
345 | }
346 | }
347 | }
348 |
349 | // MARK: - IVSPlayer.Delegate
350 |
351 | extension PlayerView: IVSPlayer.Delegate {
352 | func player(_ player: IVSPlayer, didChangeState state: IVSPlayer.State) {
353 | updateForState(state)
354 | }
355 |
356 | func player(_ player: IVSPlayer, didFailWithError error: Error) {
357 | presentError(error, componentName: "Player")
358 | }
359 |
360 | func player(_ player: IVSPlayer, didOutputCue cue: IVSCue) {
361 | switch cue {
362 | case let textMetadataCue as IVSTextMetadataCue:
363 | print("ℹ Received Timed Metadata (\(textMetadataCue.textDescription)): \(textMetadataCue.text)")
364 | guard let jsonData = textMetadataCue.text.data(using: .utf8) else {
365 | return
366 | }
367 | do {
368 | let json = try jsonDecoder.decode([String: String].self, from: jsonData)
369 | if let id = json["productId"], let product = products.first(where: { $0.id == id }) {
370 | if receivedProductsLine.last != product {
371 | receivedProductsLine.append(product)
372 | }
373 | }
374 | } catch {
375 | print("Could not decode productId: \(error)")
376 | }
377 | case let textCue as IVSTextCue:
378 | print("ℹ Received Text Cue: “\(textCue.text)”")
379 | default:
380 | print("ℹ Received unknown cue (type \(cue.type))")
381 | }
382 | }
383 | }
384 |
--------------------------------------------------------------------------------
/eCommerce/Views/Player/PlayerView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | AmazonEmber-Bold
14 |
15 |
16 | AmazonEmber-Medium
17 |
18 |
19 | AmazonEmber-Regular
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
123 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
159 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
--------------------------------------------------------------------------------
/eCommerce.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4664E33F2481198C00B75A8B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4664E33E2481198C00B75A8B /* AppDelegate.swift */; };
11 | 4664E3432481198C00B75A8B /* ProductsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4664E3422481198C00B75A8B /* ProductsViewController.swift */; };
12 | 4664E3462481198C00B75A8B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4664E3442481198C00B75A8B /* Main.storyboard */; };
13 | 4664E3482481198E00B75A8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4664E3472481198E00B75A8B /* Assets.xcassets */; };
14 | 4664E34B2481198E00B75A8B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4664E3492481198E00B75A8B /* LaunchScreen.storyboard */; };
15 | 46B18B77248E262C00BA0AF0 /* Products.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B18B76248E262C00BA0AF0 /* Products.swift */; };
16 | 5C3BD472270AFCF3004BCE01 /* PlayerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C3BD471270AFCF3004BCE01 /* PlayerView.xib */; };
17 | 5C3BD474270AFD15004BCE01 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3BD473270AFD15004BCE01 /* PlayerView.swift */; };
18 | 5C56C5F5270C826700473E52 /* ProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C56C5F4270C826700473E52 /* ProductView.swift */; };
19 | 5C56C5F7270C82AF00473E52 /* ProductView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C56C5F6270C82AF00473E52 /* ProductView.xib */; };
20 | 5C56C5FA270C8E3500473E52 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C56C5F9270C8E3500473E52 /* Image.swift */; };
21 | 5CEC613F2707426400E1C472 /* AmazonEmber-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5CEC61392707426300E1C472 /* AmazonEmber-Regular.ttf */; };
22 | 5CEC61402707426400E1C472 /* AmazonEmber-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5CEC613A2707426300E1C472 /* AmazonEmber-Bold.ttf */; };
23 | 5CEC61412707426400E1C472 /* AmazonEmber-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5CEC613B2707426300E1C472 /* AmazonEmber-Medium.ttf */; };
24 | 5CEC61422707426400E1C472 /* AmazonEmber-Heavy.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5CEC613C2707426300E1C472 /* AmazonEmber-Heavy.ttf */; };
25 | 5CEC61472707521900E1C472 /* Products.json in Resources */ = {isa = PBXBuildFile; fileRef = 5CEC61382707422200E1C472 /* Products.json */; };
26 | 5CEC61492707553600E1C472 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEC61482707553600E1C472 /* Constants.swift */; };
27 | E29B5C62A0CC8F1F22C7E1DE /* libPods-eCommerce.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B0604796E9C72975B9EAC568 /* libPods-eCommerce.a */; };
28 | /* End PBXBuildFile section */
29 |
30 | /* Begin PBXFileReference section */
31 | 2AEAF43F3A53E170ED4F9EAB /* Pods-IVSClients-eCommerce.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IVSClients-eCommerce.debug.xcconfig"; path = "../eCommerce/Pods/Target Support Files/Pods-IVSClients-eCommerce/Pods-IVSClients-eCommerce.debug.xcconfig"; sourceTree = ""; };
32 | 460A58F52490E2F6009B5B7B /* eCommerce.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = eCommerce.entitlements; sourceTree = ""; };
33 | 4664E33B2481198C00B75A8B /* eCommerce.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = eCommerce.app; sourceTree = BUILT_PRODUCTS_DIR; };
34 | 4664E33E2481198C00B75A8B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
35 | 4664E3422481198C00B75A8B /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = ""; };
36 | 4664E3452481198C00B75A8B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
37 | 4664E3472481198E00B75A8B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
38 | 4664E34A2481198E00B75A8B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
39 | 4664E34C2481198E00B75A8B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
40 | 46B18B76248E262C00BA0AF0 /* Products.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Products.swift; sourceTree = ""; };
41 | 5C3BD471270AFCF3004BCE01 /* PlayerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PlayerView.xib; sourceTree = ""; };
42 | 5C3BD473270AFD15004BCE01 /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; };
43 | 5C56C5F4270C826700473E52 /* ProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductView.swift; sourceTree = ""; };
44 | 5C56C5F6270C82AF00473E52 /* ProductView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProductView.xib; sourceTree = ""; };
45 | 5C56C5F9270C8E3500473E52 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; };
46 | 5CEC61382707422200E1C472 /* Products.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Products.json; sourceTree = ""; };
47 | 5CEC61392707426300E1C472 /* AmazonEmber-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "AmazonEmber-Regular.ttf"; sourceTree = ""; };
48 | 5CEC613A2707426300E1C472 /* AmazonEmber-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "AmazonEmber-Bold.ttf"; sourceTree = ""; };
49 | 5CEC613B2707426300E1C472 /* AmazonEmber-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "AmazonEmber-Medium.ttf"; sourceTree = ""; };
50 | 5CEC613C2707426300E1C472 /* AmazonEmber-Heavy.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "AmazonEmber-Heavy.ttf"; sourceTree = ""; };
51 | 5CEC61482707553600E1C472 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
52 | 77B6A9BA9BE03B89EBC75B09 /* Pods-eCommerce.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-eCommerce.debug.xcconfig"; path = "../amazon-ivs-ecommerce-ios-demo/Pods/Target Support Files/Pods-eCommerce/Pods-eCommerce.debug.xcconfig"; sourceTree = ""; };
53 | 83D51A534FF3FC792E25D0BC /* Pods-eCommerce.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-eCommerce.release.xcconfig"; path = "../amazon-ivs-ecommerce-ios-demo/Pods/Target Support Files/Pods-eCommerce/Pods-eCommerce.release.xcconfig"; sourceTree = ""; };
54 | B0604796E9C72975B9EAC568 /* libPods-eCommerce.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-eCommerce.a"; sourceTree = BUILT_PRODUCTS_DIR; };
55 | BE5D00185D8E188697B92D43 /* Pods-IVSClients-eCommerce.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IVSClients-eCommerce.release.xcconfig"; path = "../eCommerce/Pods/Target Support Files/Pods-IVSClients-eCommerce/Pods-IVSClients-eCommerce.release.xcconfig"; sourceTree = ""; };
56 | /* End PBXFileReference section */
57 |
58 | /* Begin PBXFrameworksBuildPhase section */
59 | 4664E3382481198C00B75A8B /* Frameworks */ = {
60 | isa = PBXFrameworksBuildPhase;
61 | buildActionMask = 2147483647;
62 | files = (
63 | E29B5C62A0CC8F1F22C7E1DE /* libPods-eCommerce.a in Frameworks */,
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | /* End PBXFrameworksBuildPhase section */
68 |
69 | /* Begin PBXGroup section */
70 | 460A58F6249228CE009B5B7B /* Custom Fonts */ = {
71 | isa = PBXGroup;
72 | children = (
73 | 5CEC613A2707426300E1C472 /* AmazonEmber-Bold.ttf */,
74 | 5CEC613C2707426300E1C472 /* AmazonEmber-Heavy.ttf */,
75 | 5CEC613B2707426300E1C472 /* AmazonEmber-Medium.ttf */,
76 | 5CEC61392707426300E1C472 /* AmazonEmber-Regular.ttf */,
77 | );
78 | path = "Custom Fonts";
79 | sourceTree = "";
80 | };
81 | 4664E3322481198C00B75A8B = {
82 | isa = PBXGroup;
83 | children = (
84 | 4664E33D2481198C00B75A8B /* eCommerce */,
85 | 4664E33C2481198C00B75A8B /* Products */,
86 | 91C8E2F2338A7F4553FD88D0 /* Pods */,
87 | A15985E20FD044D012EB2E45 /* Frameworks */,
88 | );
89 | sourceTree = "";
90 | };
91 | 4664E33C2481198C00B75A8B /* Products */ = {
92 | isa = PBXGroup;
93 | children = (
94 | 4664E33B2481198C00B75A8B /* eCommerce.app */,
95 | );
96 | name = Products;
97 | sourceTree = "";
98 | };
99 | 4664E33D2481198C00B75A8B /* eCommerce */ = {
100 | isa = PBXGroup;
101 | children = (
102 | 460A58F52490E2F6009B5B7B /* eCommerce.entitlements */,
103 | 4664E33E2481198C00B75A8B /* AppDelegate.swift */,
104 | 5CEC61382707422200E1C472 /* Products.json */,
105 | 46B18B78248E29BC00BA0AF0 /* Models */,
106 | 4674B023248E322B0047FD37 /* Views */,
107 | 46B18B7B248E29E500BA0AF0 /* Controllers */,
108 | 4664E3472481198E00B75A8B /* Assets.xcassets */,
109 | 5CEC61482707553600E1C472 /* Constants.swift */,
110 | 4664E3442481198C00B75A8B /* Main.storyboard */,
111 | 4664E3492481198E00B75A8B /* LaunchScreen.storyboard */,
112 | 4664E34C2481198E00B75A8B /* Info.plist */,
113 | 460A58F6249228CE009B5B7B /* Custom Fonts */,
114 | );
115 | path = eCommerce;
116 | sourceTree = "";
117 | };
118 | 4674B023248E322B0047FD37 /* Views */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 5C56C5F8270C82B500473E52 /* Product */,
122 | 5C3BD475270B000A004BCE01 /* Player */,
123 | );
124 | path = Views;
125 | sourceTree = "";
126 | };
127 | 46B18B78248E29BC00BA0AF0 /* Models */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 46B18B76248E262C00BA0AF0 /* Products.swift */,
131 | 5C56C5F9270C8E3500473E52 /* Image.swift */,
132 | );
133 | path = Models;
134 | sourceTree = "";
135 | };
136 | 46B18B7B248E29E500BA0AF0 /* Controllers */ = {
137 | isa = PBXGroup;
138 | children = (
139 | 4664E3422481198C00B75A8B /* ProductsViewController.swift */,
140 | );
141 | path = Controllers;
142 | sourceTree = "";
143 | };
144 | 5C3BD475270B000A004BCE01 /* Player */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 5C3BD473270AFD15004BCE01 /* PlayerView.swift */,
148 | 5C3BD471270AFCF3004BCE01 /* PlayerView.xib */,
149 | );
150 | path = Player;
151 | sourceTree = "";
152 | };
153 | 5C56C5F8270C82B500473E52 /* Product */ = {
154 | isa = PBXGroup;
155 | children = (
156 | 5C56C5F4270C826700473E52 /* ProductView.swift */,
157 | 5C56C5F6270C82AF00473E52 /* ProductView.xib */,
158 | );
159 | path = Product;
160 | sourceTree = "";
161 | };
162 | 91C8E2F2338A7F4553FD88D0 /* Pods */ = {
163 | isa = PBXGroup;
164 | children = (
165 | 2AEAF43F3A53E170ED4F9EAB /* Pods-IVSClients-eCommerce.debug.xcconfig */,
166 | BE5D00185D8E188697B92D43 /* Pods-IVSClients-eCommerce.release.xcconfig */,
167 | 77B6A9BA9BE03B89EBC75B09 /* Pods-eCommerce.debug.xcconfig */,
168 | 83D51A534FF3FC792E25D0BC /* Pods-eCommerce.release.xcconfig */,
169 | );
170 | name = Pods;
171 | path = ../Pods;
172 | sourceTree = "";
173 | };
174 | A15985E20FD044D012EB2E45 /* Frameworks */ = {
175 | isa = PBXGroup;
176 | children = (
177 | B0604796E9C72975B9EAC568 /* libPods-eCommerce.a */,
178 | );
179 | name = Frameworks;
180 | sourceTree = "";
181 | };
182 | /* End PBXGroup section */
183 |
184 | /* Begin PBXNativeTarget section */
185 | 4664E33A2481198C00B75A8B /* eCommerce */ = {
186 | isa = PBXNativeTarget;
187 | buildConfigurationList = 4664E34F2481198E00B75A8B /* Build configuration list for PBXNativeTarget "eCommerce" */;
188 | buildPhases = (
189 | 82FFFFD775BD0E61B119FD5F /* [CP] Check Pods Manifest.lock */,
190 | 4664E3372481198C00B75A8B /* Sources */,
191 | 4664E3382481198C00B75A8B /* Frameworks */,
192 | 4664E3392481198C00B75A8B /* Resources */,
193 | D6A3B8A895ACD01F9D1E3776 /* [CP] Embed Pods Frameworks */,
194 | );
195 | buildRules = (
196 | );
197 | dependencies = (
198 | );
199 | name = eCommerce;
200 | productName = eCommerce;
201 | productReference = 4664E33B2481198C00B75A8B /* eCommerce.app */;
202 | productType = "com.apple.product-type.application";
203 | };
204 | /* End PBXNativeTarget section */
205 |
206 | /* Begin PBXProject section */
207 | 4664E3332481198C00B75A8B /* Project object */ = {
208 | isa = PBXProject;
209 | attributes = {
210 | LastSwiftUpdateCheck = 1130;
211 | LastUpgradeCheck = 1130;
212 | ORGANIZATIONNAME = Twitch;
213 | TargetAttributes = {
214 | 4664E33A2481198C00B75A8B = {
215 | CreatedOnToolsVersion = 11.3;
216 | };
217 | };
218 | };
219 | buildConfigurationList = 4664E3362481198C00B75A8B /* Build configuration list for PBXProject "eCommerce" */;
220 | compatibilityVersion = "Xcode 9.3";
221 | developmentRegion = en;
222 | hasScannedForEncodings = 0;
223 | knownRegions = (
224 | en,
225 | Base,
226 | );
227 | mainGroup = 4664E3322481198C00B75A8B;
228 | productRefGroup = 4664E33C2481198C00B75A8B /* Products */;
229 | projectDirPath = "";
230 | projectRoot = "";
231 | targets = (
232 | 4664E33A2481198C00B75A8B /* eCommerce */,
233 | );
234 | };
235 | /* End PBXProject section */
236 |
237 | /* Begin PBXResourcesBuildPhase section */
238 | 4664E3392481198C00B75A8B /* Resources */ = {
239 | isa = PBXResourcesBuildPhase;
240 | buildActionMask = 2147483647;
241 | files = (
242 | 4664E34B2481198E00B75A8B /* LaunchScreen.storyboard in Resources */,
243 | 5CEC61412707426400E1C472 /* AmazonEmber-Medium.ttf in Resources */,
244 | 5C3BD472270AFCF3004BCE01 /* PlayerView.xib in Resources */,
245 | 5CEC613F2707426400E1C472 /* AmazonEmber-Regular.ttf in Resources */,
246 | 4664E3482481198E00B75A8B /* Assets.xcassets in Resources */,
247 | 5CEC61402707426400E1C472 /* AmazonEmber-Bold.ttf in Resources */,
248 | 5CEC61422707426400E1C472 /* AmazonEmber-Heavy.ttf in Resources */,
249 | 4664E3462481198C00B75A8B /* Main.storyboard in Resources */,
250 | 5C56C5F7270C82AF00473E52 /* ProductView.xib in Resources */,
251 | 5CEC61472707521900E1C472 /* Products.json in Resources */,
252 | );
253 | runOnlyForDeploymentPostprocessing = 0;
254 | };
255 | /* End PBXResourcesBuildPhase section */
256 |
257 | /* Begin PBXShellScriptBuildPhase section */
258 | 82FFFFD775BD0E61B119FD5F /* [CP] Check Pods Manifest.lock */ = {
259 | isa = PBXShellScriptBuildPhase;
260 | buildActionMask = 2147483647;
261 | files = (
262 | );
263 | inputFileListPaths = (
264 | );
265 | inputPaths = (
266 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
267 | "${PODS_ROOT}/Manifest.lock",
268 | );
269 | name = "[CP] Check Pods Manifest.lock";
270 | outputFileListPaths = (
271 | );
272 | outputPaths = (
273 | "$(DERIVED_FILE_DIR)/Pods-eCommerce-checkManifestLockResult.txt",
274 | );
275 | runOnlyForDeploymentPostprocessing = 0;
276 | shellPath = /bin/sh;
277 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
278 | showEnvVarsInLog = 0;
279 | };
280 | D6A3B8A895ACD01F9D1E3776 /* [CP] Embed Pods Frameworks */ = {
281 | isa = PBXShellScriptBuildPhase;
282 | buildActionMask = 2147483647;
283 | files = (
284 | );
285 | inputFileListPaths = (
286 | "${PODS_ROOT}/Target Support Files/Pods-eCommerce/Pods-eCommerce-frameworks-${CONFIGURATION}-input-files.xcfilelist",
287 | );
288 | name = "[CP] Embed Pods Frameworks";
289 | outputFileListPaths = (
290 | "${PODS_ROOT}/Target Support Files/Pods-eCommerce/Pods-eCommerce-frameworks-${CONFIGURATION}-output-files.xcfilelist",
291 | );
292 | runOnlyForDeploymentPostprocessing = 0;
293 | shellPath = /bin/sh;
294 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-eCommerce/Pods-eCommerce-frameworks.sh\"\n";
295 | showEnvVarsInLog = 0;
296 | };
297 | /* End PBXShellScriptBuildPhase section */
298 |
299 | /* Begin PBXSourcesBuildPhase section */
300 | 4664E3372481198C00B75A8B /* Sources */ = {
301 | isa = PBXSourcesBuildPhase;
302 | buildActionMask = 2147483647;
303 | files = (
304 | 5C56C5FA270C8E3500473E52 /* Image.swift in Sources */,
305 | 5C56C5F5270C826700473E52 /* ProductView.swift in Sources */,
306 | 5CEC61492707553600E1C472 /* Constants.swift in Sources */,
307 | 4664E3432481198C00B75A8B /* ProductsViewController.swift in Sources */,
308 | 5C3BD474270AFD15004BCE01 /* PlayerView.swift in Sources */,
309 | 4664E33F2481198C00B75A8B /* AppDelegate.swift in Sources */,
310 | 46B18B77248E262C00BA0AF0 /* Products.swift in Sources */,
311 | );
312 | runOnlyForDeploymentPostprocessing = 0;
313 | };
314 | /* End PBXSourcesBuildPhase section */
315 |
316 | /* Begin PBXVariantGroup section */
317 | 4664E3442481198C00B75A8B /* Main.storyboard */ = {
318 | isa = PBXVariantGroup;
319 | children = (
320 | 4664E3452481198C00B75A8B /* Base */,
321 | );
322 | name = Main.storyboard;
323 | sourceTree = "";
324 | };
325 | 4664E3492481198E00B75A8B /* LaunchScreen.storyboard */ = {
326 | isa = PBXVariantGroup;
327 | children = (
328 | 4664E34A2481198E00B75A8B /* Base */,
329 | );
330 | name = LaunchScreen.storyboard;
331 | sourceTree = "";
332 | };
333 | /* End PBXVariantGroup section */
334 |
335 | /* Begin XCBuildConfiguration section */
336 | 4664E34D2481198E00B75A8B /* Debug */ = {
337 | isa = XCBuildConfiguration;
338 | buildSettings = {
339 | ALWAYS_SEARCH_USER_PATHS = NO;
340 | CLANG_ANALYZER_NONNULL = YES;
341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
343 | CLANG_CXX_LIBRARY = "libc++";
344 | CLANG_ENABLE_MODULES = YES;
345 | CLANG_ENABLE_OBJC_ARC = YES;
346 | CLANG_ENABLE_OBJC_WEAK = YES;
347 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
348 | CLANG_WARN_BOOL_CONVERSION = YES;
349 | CLANG_WARN_COMMA = YES;
350 | CLANG_WARN_CONSTANT_CONVERSION = YES;
351 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
353 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
354 | CLANG_WARN_EMPTY_BODY = YES;
355 | CLANG_WARN_ENUM_CONVERSION = YES;
356 | CLANG_WARN_INFINITE_RECURSION = YES;
357 | CLANG_WARN_INT_CONVERSION = YES;
358 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
359 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
360 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
361 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
363 | CLANG_WARN_STRICT_PROTOTYPES = YES;
364 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
365 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
366 | CLANG_WARN_UNREACHABLE_CODE = YES;
367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
368 | COPY_PHASE_STRIP = NO;
369 | DEBUG_INFORMATION_FORMAT = dwarf;
370 | ENABLE_STRICT_OBJC_MSGSEND = YES;
371 | ENABLE_TESTABILITY = YES;
372 | GCC_C_LANGUAGE_STANDARD = gnu11;
373 | GCC_DYNAMIC_NO_PIC = NO;
374 | GCC_NO_COMMON_BLOCKS = YES;
375 | GCC_OPTIMIZATION_LEVEL = 0;
376 | GCC_PREPROCESSOR_DEFINITIONS = (
377 | "DEBUG=1",
378 | "$(inherited)",
379 | );
380 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
381 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
382 | GCC_WARN_UNDECLARED_SELECTOR = YES;
383 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
384 | GCC_WARN_UNUSED_FUNCTION = YES;
385 | GCC_WARN_UNUSED_VARIABLE = YES;
386 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
387 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
388 | MTL_FAST_MATH = YES;
389 | ONLY_ACTIVE_ARCH = YES;
390 | SDKROOT = iphoneos;
391 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
392 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
393 | };
394 | name = Debug;
395 | };
396 | 4664E34E2481198E00B75A8B /* Release */ = {
397 | isa = XCBuildConfiguration;
398 | buildSettings = {
399 | ALWAYS_SEARCH_USER_PATHS = NO;
400 | CLANG_ANALYZER_NONNULL = YES;
401 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
402 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
403 | CLANG_CXX_LIBRARY = "libc++";
404 | CLANG_ENABLE_MODULES = YES;
405 | CLANG_ENABLE_OBJC_ARC = YES;
406 | CLANG_ENABLE_OBJC_WEAK = YES;
407 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
408 | CLANG_WARN_BOOL_CONVERSION = YES;
409 | CLANG_WARN_COMMA = YES;
410 | CLANG_WARN_CONSTANT_CONVERSION = YES;
411 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
412 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
413 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
414 | CLANG_WARN_EMPTY_BODY = YES;
415 | CLANG_WARN_ENUM_CONVERSION = YES;
416 | CLANG_WARN_INFINITE_RECURSION = YES;
417 | CLANG_WARN_INT_CONVERSION = YES;
418 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
419 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
420 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
423 | CLANG_WARN_STRICT_PROTOTYPES = YES;
424 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
425 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
426 | CLANG_WARN_UNREACHABLE_CODE = YES;
427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
428 | COPY_PHASE_STRIP = NO;
429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
430 | ENABLE_NS_ASSERTIONS = NO;
431 | ENABLE_STRICT_OBJC_MSGSEND = YES;
432 | GCC_C_LANGUAGE_STANDARD = gnu11;
433 | GCC_NO_COMMON_BLOCKS = YES;
434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
436 | GCC_WARN_UNDECLARED_SELECTOR = YES;
437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
438 | GCC_WARN_UNUSED_FUNCTION = YES;
439 | GCC_WARN_UNUSED_VARIABLE = YES;
440 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
441 | MTL_ENABLE_DEBUG_INFO = NO;
442 | MTL_FAST_MATH = YES;
443 | SDKROOT = iphoneos;
444 | SWIFT_COMPILATION_MODE = wholemodule;
445 | SWIFT_OPTIMIZATION_LEVEL = "-O";
446 | VALIDATE_PRODUCT = YES;
447 | };
448 | name = Release;
449 | };
450 | 4664E3502481198E00B75A8B /* Debug */ = {
451 | isa = XCBuildConfiguration;
452 | baseConfigurationReference = 77B6A9BA9BE03B89EBC75B09 /* Pods-eCommerce.debug.xcconfig */;
453 | buildSettings = {
454 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
455 | CODE_SIGN_ENTITLEMENTS = eCommerce/eCommerce.entitlements;
456 | CODE_SIGN_STYLE = Automatic;
457 | DEVELOPMENT_TEAM = "";
458 | INFOPLIST_FILE = eCommerce/Info.plist;
459 | LD_RUNPATH_SEARCH_PATHS = (
460 | "$(inherited)",
461 | "@executable_path/Frameworks",
462 | );
463 | PRODUCT_BUNDLE_IDENTIFIER = tv.twitch.starfruit.demo.eCommerce;
464 | PRODUCT_NAME = "$(TARGET_NAME)";
465 | SWIFT_VERSION = 5.0;
466 | TARGETED_DEVICE_FAMILY = "1,2";
467 | };
468 | name = Debug;
469 | };
470 | 4664E3512481198E00B75A8B /* Release */ = {
471 | isa = XCBuildConfiguration;
472 | baseConfigurationReference = 83D51A534FF3FC792E25D0BC /* Pods-eCommerce.release.xcconfig */;
473 | buildSettings = {
474 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
475 | CODE_SIGN_ENTITLEMENTS = eCommerce/eCommerce.entitlements;
476 | CODE_SIGN_STYLE = Automatic;
477 | DEVELOPMENT_TEAM = "";
478 | INFOPLIST_FILE = eCommerce/Info.plist;
479 | LD_RUNPATH_SEARCH_PATHS = (
480 | "$(inherited)",
481 | "@executable_path/Frameworks",
482 | );
483 | PRODUCT_BUNDLE_IDENTIFIER = tv.twitch.starfruit.demo.eCommerce;
484 | PRODUCT_NAME = "$(TARGET_NAME)";
485 | SWIFT_VERSION = 5.0;
486 | TARGETED_DEVICE_FAMILY = "1,2";
487 | };
488 | name = Release;
489 | };
490 | /* End XCBuildConfiguration section */
491 |
492 | /* Begin XCConfigurationList section */
493 | 4664E3362481198C00B75A8B /* Build configuration list for PBXProject "eCommerce" */ = {
494 | isa = XCConfigurationList;
495 | buildConfigurations = (
496 | 4664E34D2481198E00B75A8B /* Debug */,
497 | 4664E34E2481198E00B75A8B /* Release */,
498 | );
499 | defaultConfigurationIsVisible = 0;
500 | defaultConfigurationName = Release;
501 | };
502 | 4664E34F2481198E00B75A8B /* Build configuration list for PBXNativeTarget "eCommerce" */ = {
503 | isa = XCConfigurationList;
504 | buildConfigurations = (
505 | 4664E3502481198E00B75A8B /* Debug */,
506 | 4664E3512481198E00B75A8B /* Release */,
507 | );
508 | defaultConfigurationIsVisible = 0;
509 | defaultConfigurationName = Release;
510 | };
511 | /* End XCConfigurationList section */
512 | };
513 | rootObject = 4664E3332481198C00B75A8B /* Project object */;
514 | }
515 |
--------------------------------------------------------------------------------