aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/tvOSSample/tvOSSample
diff options
context:
space:
mode:
Diffstat (limited to 'Example/tvOSSample/tvOSSample')
-rw-r--r--Example/tvOSSample/tvOSSample/AppDelegate.swift29
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json17
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json17
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json32
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json16
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json6
-rw-r--r--Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json22
-rw-r--r--Example/tvOSSample/tvOSSample/AuthLoginViewController.swift40
-rw-r--r--Example/tvOSSample/tvOSSample/AuthViewController.swift86
-rw-r--r--Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard355
-rw-r--r--Example/tvOSSample/tvOSSample/DatabaseViewController.swift83
-rw-r--r--Example/tvOSSample/tvOSSample/EmailLoginViewController.swift93
-rw-r--r--Example/tvOSSample/tvOSSample/Info.plist32
-rw-r--r--Example/tvOSSample/tvOSSample/StorageViewController.swift148
27 files changed, 1109 insertions, 0 deletions
diff --git a/Example/tvOSSample/tvOSSample/AppDelegate.swift b/Example/tvOSSample/tvOSSample/AppDelegate.swift
new file mode 100644
index 0000000..9a0d052
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/AppDelegate.swift
@@ -0,0 +1,29 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import UIKit
+import FirebaseCore
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ FirebaseApp.configure()
+ return true
+ }
+}
+
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 0000000..b03ded1
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "size" : "1280x768",
+ "idiom" : "tv",
+ "filename" : "App Icon - App Store.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "400x240",
+ "idiom" : "tv",
+ "filename" : "App Icon.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "2320x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image Wide.imageset",
+ "role" : "top-shelf-image-wide"
+ },
+ {
+ "size" : "1920x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image.imageset",
+ "role" : "top-shelf-image"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json b/Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000..d746a60
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "11.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "9.0",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/Example/tvOSSample/tvOSSample/AuthLoginViewController.swift b/Example/tvOSSample/tvOSSample/AuthLoginViewController.swift
new file mode 100644
index 0000000..dcf72d4
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/AuthLoginViewController.swift
@@ -0,0 +1,40 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import UIKit
+import FirebaseAuth
+
+class AuthLoginViewController: UIViewController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Do any additional setup after loading the view.
+ }
+
+ override func didReceiveMemoryWarning() {
+ super.didReceiveMemoryWarning()
+ // Dispose of any resources that can be recreated.
+ }
+
+ /*
+ // MARK: - Navigation
+
+ // In a storyboard-based application, you will often want to do a little preparation before navigation
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ // Get the new view controller using segue.destinationViewController.
+ // Pass the selected object to the new view controller.
+ }
+ */
+}
diff --git a/Example/tvOSSample/tvOSSample/AuthViewController.swift b/Example/tvOSSample/tvOSSample/AuthViewController.swift
new file mode 100644
index 0000000..72351d3
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/AuthViewController.swift
@@ -0,0 +1,86 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import UIKit
+import FirebaseAuth
+
+class AuthViewController: UIViewController {
+
+ // MARK: - User Interface
+
+ /// A stackview containing all of the buttons to providers (Email, OAuth, etc).
+ @IBOutlet weak var providers: UIStackView!
+
+ /// A stackview containing a signed in label and sign out button.
+ @IBOutlet weak var signedIn: UIStackView!
+
+ /// A label to display the status for the signed in user.
+ @IBOutlet weak var signInStatus: UILabel!
+
+ // MARK: - User Actions
+
+ @IBAction func signOutButtonHit(_ sender: UIButton) {
+ // Sign out via Auth and update the UI.
+ try? Auth.auth().signOut()
+
+ setUserSignedIn(nil)
+ }
+
+ // MARK: - View Controller Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Update the UI based on the current user (if there is one).
+ setUserSignedIn(Auth.auth().currentUser)
+ }
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ let destination = segue.destination
+ if let emailVC = destination as? EmailLoginViewController {
+ emailVC.delegate = self
+ }
+ }
+
+ // MARK: - Internal Helpers
+
+ private func setUserSignedIn(_ user: User?) {
+ if let user = user {
+ providers.isHidden = true
+ signedIn.isHidden = false
+
+ signInStatus.text = "User is signed in via \(user.providerID) and the UID \(user.uid)"
+ } else {
+ // User is signed out, hide the signed in state and show the providers.
+ providers.isHidden = false
+ signedIn.isHidden = true
+ }
+ }
+}
+
+// MARK: - EmailLoginDelegate conformance.
+
+extension AuthViewController: EmailLoginDelegate {
+ func emailLogin(_ controller: EmailLoginViewController, signedInAs user: User) {
+ setUserSignedIn(user)
+ dismiss(animated: true)
+ }
+
+ func emailLogin(_ controller: EmailLoginViewController, failedWithError error: Error) {
+ print("Fail..... \(error)")
+ DispatchQueue.main.async {
+ controller.presentError(with: "There was an issue logging in. Please try again.")
+ }
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard b/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..a2539b3
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard
@@ -0,0 +1,355 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13529" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="7zF-dN-6Yw">
+ <device id="appleTV" orientation="landscape">
+ <adaptation id="light"/>
+ </device>
+ <dependencies>
+ <deployment identifier="tvOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <customFonts key="customFonts">
+ <array key="HelveticaNeue.ttc">
+ <string>HelveticaNeue</string>
+ </array>
+ </customFonts>
+ <scenes>
+ <!--Storage-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController title="Storage" id="BYZ-38-t0r" customClass="StorageViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="G46-SV-2zY">
+ <rect key="frame" x="90" y="60" width="1740" height="960"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="Hello, Firebase!" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g3G-9d-w9c">
+ <rect key="frame" x="628" y="0.0" width="484" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="1Ht-Xf-hyX">
+ <rect key="frame" x="0.0" y="111" width="1740" height="677"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="State: Pending download" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4sv-2l-7R6">
+ <rect key="frame" x="657" y="808" width="427" height="46"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="253" verticalCompressionResistancePriority="749" distribution="fillEqually" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="Pcd-oU-DKI" userLabel="Button Stack View">
+ <rect key="frame" x="589" y="874" width="562" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ERN-SX-QsK">
+ <rect key="frame" x="0.0" y="0.0" width="251" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Download"/>
+ <connections>
+ <action selector="downloadButtonHit:" destination="BYZ-38-t0r" eventType="primaryActionTriggered" id="wUX-Vk-IME"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tx1-wP-N8v">
+ <rect key="frame" x="311" y="0.0" width="251" height="86"/>
+ <color key="backgroundColor" red="1" green="0.40000000600000002" blue="0.40000000600000002" alpha="1" colorSpace="calibratedRGB"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Clear"/>
+ <connections>
+ <action selector="clearButtonHit:" destination="BYZ-38-t0r" eventType="primaryActionTriggered" id="s8P-pU-T19"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ </subviews>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstItem="G46-SV-2zY" firstAttribute="leading" secondItem="wu6-TO-1qx" secondAttribute="leading" id="HA1-L9-mG6"/>
+ <constraint firstItem="wu6-TO-1qx" firstAttribute="trailing" secondItem="G46-SV-2zY" secondAttribute="trailing" id="f82-FP-nqR"/>
+ <constraint firstItem="G46-SV-2zY" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="gcN-NC-PDe"/>
+ <constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="G46-SV-2zY" secondAttribute="bottom" id="hb7-C3-J4p"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
+ </view>
+ <extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
+ <tabBarItem key="tabBarItem" title="Storage" id="4mO-Ls-vgJ"/>
+ <connections>
+ <outlet property="clearButton" destination="tx1-wP-N8v" id="lQA-AQ-sT6"/>
+ <outlet property="downloadButton" destination="ERN-SX-QsK" id="2Kf-3u-AxO"/>
+ <outlet property="imageView" destination="1Ht-Xf-hyX" id="MeV-di-b99"/>
+ <outlet property="stateLabel" destination="4sv-2l-7R6" id="DKm-L2-aiR"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-3032" y="1936"/>
+ </scene>
+ <!--Database-->
+ <scene sceneID="i8D-25-rvO">
+ <objects>
+ <viewController id="gup-Ft-HnK" customClass="DatabaseViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="i5o-My-7RI"/>
+ <viewControllerLayoutGuide type="bottom" id="R20-Wh-bn4"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="1lM-LN-Cey">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Bah-Ek-V99">
+ <rect key="frame" x="90" y="60" width="1740" height="960"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="253" verticalCompressionResistancePriority="1000" text="Magic Syncing Counter" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pjh-c5-D2o">
+ <rect key="frame" x="0.0" y="0.0" width="1740" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalCompressionResistancePriority="900" text="?" textAlignment="center" lineBreakMode="tailTruncation" minimumScaleFactor="0.20000000000000001" translatesAutoresizingMaskIntoConstraints="NO" id="gcg-TC-hY4">
+ <rect key="frame" x="0.0" y="111" width="1740" height="743"/>
+ <fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="700"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="253" verticalCompressionResistancePriority="749" distribution="fillEqually" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="1hw-2E-6dg" userLabel="Button Stack View">
+ <rect key="frame" x="0.0" y="874" width="1740" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Yc7-OR-WVJ">
+ <rect key="frame" x="0.0" y="0.0" width="840" height="86"/>
+ <color key="backgroundColor" red="0.25098040700000002" green="0.50196081400000003" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="+1"/>
+ <connections>
+ <action selector="incrementButtonHit:" destination="gup-Ft-HnK" eventType="primaryActionTriggered" id="zR6-AT-sGq"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ams-4H-2D4">
+ <rect key="frame" x="900" y="0.0" width="840" height="86"/>
+ <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="-1"/>
+ <connections>
+ <action selector="decrementButton:" destination="gup-Ft-HnK" eventType="primaryActionTriggered" id="KTe-Dn-DgZ"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="R20-Wh-bn4" firstAttribute="top" secondItem="Bah-Ek-V99" secondAttribute="bottom" id="ECo-G5-TMU"/>
+ <constraint firstItem="V8l-aL-U1D" firstAttribute="trailing" secondItem="Bah-Ek-V99" secondAttribute="trailing" id="Ikr-TA-Ejk"/>
+ <constraint firstItem="Bah-Ek-V99" firstAttribute="leading" secondItem="V8l-aL-U1D" secondAttribute="leading" id="L8R-dY-jge"/>
+ <constraint firstItem="Bah-Ek-V99" firstAttribute="top" secondItem="i5o-My-7RI" secondAttribute="bottom" id="nvx-MN-rDc"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="V8l-aL-U1D"/>
+ </view>
+ <extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
+ <tabBarItem key="tabBarItem" title="Database" id="4ry-Ig-tAU"/>
+ <connections>
+ <outlet property="currentValue" destination="gcg-TC-hY4" id="1Cs-5w-yPF"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="FDt-0K-Tad" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-812" y="1936"/>
+ </scene>
+ <!--Firebase Demo-->
+ <scene sceneID="Cbs-Et-MXF">
+ <objects>
+ <tabBarController title="Firebase Demo" automaticallyAdjustsScrollViewInsets="NO" id="7zF-dN-6Yw" sceneMemberID="viewController">
+ <toolbarItems/>
+ <tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="yhw-Ot-Uul">
+ <rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tabBar>
+ <connections>
+ <segue destination="BYZ-38-t0r" kind="relationship" relationship="viewControllers" id="he8-oR-peB"/>
+ <segue destination="XYg-pc-Kbc" kind="relationship" relationship="viewControllers" id="x6X-oT-xPX"/>
+ <segue destination="gup-Ft-HnK" kind="relationship" relationship="viewControllers" id="CM1-Uf-Vcz"/>
+ </connections>
+ </tabBarController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="2Mm-5J-pE9" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-814" y="-224"/>
+ </scene>
+ <!--Auth-->
+ <scene sceneID="KOh-Uh-VKO">
+ <objects>
+ <viewController id="XYg-pc-Kbc" customClass="AuthViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="q8D-PQ-udq"/>
+ <viewControllerLayoutGuide type="bottom" id="w5r-DR-UCF"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8lE-2P-i4i">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="500" verticalCompressionResistancePriority="1000" text="Auth" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GfO-0W-QKE">
+ <rect key="frame" x="885" y="60" width="151" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="jeZ-ht-4RO" userLabel="Outer Stack">
+ <rect key="frame" x="769" y="497" width="383" height="86"/>
+ <subviews>
+ <stackView hidden="YES" opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="ljv-p2-x1f" userLabel="Signed In Stack">
+ <rect key="frame" x="0.0" y="-497" width="383" height="60"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Signed in: (UID)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j4e-N4-2IJ">
+ <rect key="frame" x="57" y="0.0" width="269" height="0.0"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m3A-YK-ass">
+ <rect key="frame" x="78" y="60" width="228" height="0.0"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Sign Out"/>
+ <connections>
+ <action selector="signOutButtonHit:" destination="XYg-pc-Kbc" eventType="primaryActionTriggered" id="Hiq-Jx-oNo"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="500" verticalCompressionResistancePriority="749" axis="vertical" distribution="fillEqually" alignment="center" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="0bY-NY-sEX" userLabel="Other Providers Stack">
+ <rect key="frame" x="0.0" y="0.0" width="383" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FRK-uH-meZ">
+ <rect key="frame" x="0.0" y="0.0" width="383" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Email &amp; Password"/>
+ <connections>
+ <segue destination="64h-nN-haS" kind="show" id="OBj-mt-jsE"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="jeZ-ht-4RO" firstAttribute="top" relation="greaterThanOrEqual" secondItem="GfO-0W-QKE" secondAttribute="bottom" id="HyJ-ip-Q1e"/>
+ <constraint firstItem="GfO-0W-QKE" firstAttribute="top" secondItem="q8D-PQ-udq" secondAttribute="bottom" id="Icd-az-gH7"/>
+ <constraint firstItem="GfO-0W-QKE" firstAttribute="centerX" secondItem="8lE-2P-i4i" secondAttribute="centerX" id="q5o-7T-CbO"/>
+ <constraint firstItem="jeZ-ht-4RO" firstAttribute="centerY" secondItem="8lE-2P-i4i" secondAttribute="centerY" id="tii-97-jog"/>
+ <constraint firstItem="jeZ-ht-4RO" firstAttribute="centerX" secondItem="8lE-2P-i4i" secondAttribute="centerX" id="u5t-zJ-nRH"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="Kvt-PZ-Nk9"/>
+ </view>
+ <extendedEdge key="edgesForExtendedLayout"/>
+ <tabBarItem key="tabBarItem" title="Auth" id="IQh-1s-utZ"/>
+ <connections>
+ <outlet property="providers" destination="0bY-NY-sEX" id="8oq-4v-UHT"/>
+ <outlet property="signInStatus" destination="j4e-N4-2IJ" id="7wt-c1-8N9"/>
+ <outlet property="signedIn" destination="ljv-p2-x1f" id="4IZ-Bm-XNW"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="7sk-w5-jZ7" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="1350" y="1936"/>
+ </scene>
+ <!--Email Login View Controller-->
+ <scene sceneID="QMw-Qt-hDM">
+ <objects>
+ <viewController id="64h-nN-haS" customClass="EmailLoginViewController" customModule="tvOSSample" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="4fg-KG-aTM"/>
+ <viewControllerLayoutGuide type="bottom" id="RkR-wX-Tdq"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="yhK-2X-Z3p">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="Email Login" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46l-Z7-4KI">
+ <rect key="frame" x="778" y="20" width="365" height="91"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="A1D-vt-Yfv">
+ <rect key="frame" x="716" y="409" width="490" height="262"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="8Ag-kk-FiI" userLabel="Email Inputs">
+ <rect key="frame" x="0.0" y="0.0" width="490" height="136"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" axis="vertical" distribution="fillEqually" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="OMj-ve-Czq" userLabel="Email Fields Stack">
+ <rect key="frame" x="0.0" y="0.0" width="490" height="136"/>
+ <subviews>
+ <textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" horizontalCompressionResistancePriority="800" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Email Address" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Jwa-bJ-Tt6">
+ <rect key="frame" x="0.0" y="0.0" width="490" height="58"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
+ <textInputTraits key="textInputTraits" textContentType="email"/>
+ </textField>
+ <textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="100" horizontalCompressionResistancePriority="800" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Password" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="GP0-E2-Zjx">
+ <rect key="frame" x="0.0" y="78" width="490" height="58"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
+ <textInputTraits key="textInputTraits" secureTextEntry="YES" textContentType="password"/>
+ </textField>
+ </subviews>
+ </stackView>
+ </subviews>
+ </stackView>
+ <stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="256" verticalCompressionResistancePriority="1000" distribution="fillEqually" alignment="center" spacing="60" translatesAutoresizingMaskIntoConstraints="NO" id="6pe-KG-Tt8" userLabel="Email Buttons Stack">
+ <rect key="frame" x="0.0" y="176" width="490" height="86"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JUJ-6s-duw">
+ <rect key="frame" x="0.0" y="0.0" width="215" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Log In"/>
+ <connections>
+ <action selector="logInButtonHit:" destination="64h-nN-haS" eventType="primaryActionTriggered" id="Ea2-dP-bgD"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Dt9-2d-WJM">
+ <rect key="frame" x="275" y="0.0" width="215" height="86"/>
+ <inset key="contentEdgeInsets" minX="40" minY="20" maxX="40" maxY="20"/>
+ <state key="normal" title="Sign Up"/>
+ <connections>
+ <action selector="signUpButtonHit:" destination="64h-nN-haS" eventType="primaryActionTriggered" id="KUW-Xi-zeI"/>
+ </connections>
+ </button>
+ </subviews>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="6pe-KG-Tt8" firstAttribute="width" secondItem="8Ag-kk-FiI" secondAttribute="width" id="si6-Rj-cKU"/>
+ </constraints>
+ </stackView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="A1D-vt-Yfv" firstAttribute="centerY" secondItem="yhK-2X-Z3p" secondAttribute="centerY" id="AsP-r2-CmU"/>
+ <constraint firstItem="46l-Z7-4KI" firstAttribute="centerX" secondItem="yhK-2X-Z3p" secondAttribute="centerX" id="FSK-gW-wzc"/>
+ <constraint firstItem="A1D-vt-Yfv" firstAttribute="centerX" secondItem="46l-Z7-4KI" secondAttribute="centerX" id="SRe-fq-sjl"/>
+ <constraint firstItem="46l-Z7-4KI" firstAttribute="top" secondItem="yhK-2X-Z3p" secondAttribute="top" constant="20" id="WYg-tl-PPG"/>
+ <constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="A1D-vt-Yfv" secondAttribute="bottom" id="b2e-xU-0xq"/>
+ <constraint firstItem="A1D-vt-Yfv" firstAttribute="top" relation="greaterThanOrEqual" secondItem="46l-Z7-4KI" secondAttribute="bottom" id="bj1-cy-X9f"/>
+ </constraints>
+ <viewLayoutGuide key="safeArea" id="mS6-Be-asr"/>
+ </view>
+ <value key="contentSizeForViewInPopover" type="size" width="840" height="385"/>
+ <connections>
+ <outlet property="emailAddress" destination="Jwa-bJ-Tt6" id="HOE-at-pka"/>
+ <outlet property="password" destination="GP0-E2-Zjx" id="X8f-Ew-moe"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="guA-tW-goc" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="447" y="4052"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/tvOSSample/tvOSSample/DatabaseViewController.swift b/Example/tvOSSample/tvOSSample/DatabaseViewController.swift
new file mode 100644
index 0000000..712c48f
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/DatabaseViewController.swift
@@ -0,0 +1,83 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import UIKit
+import FirebaseDatabase
+
+/// A class to demonstrate the Firebase Realtime Database API. This will show a number read
+/// from the Database and increase or decrease it based on the buttons pressed.
+class DatabaseViewController: UIViewController {
+ private enum Counter: Int {
+ case increment = 1
+ case decrement = -1
+
+ var intValue: Int {
+ return rawValue
+ }
+ }
+
+ // MARK: - Interface
+
+ /// Label to display the current value.
+ @IBOutlet weak var currentValue: UILabel!
+
+ // MARK: - User Actions
+
+ /// The increment button was hit.
+ @IBAction func incrementButtonHit(_ sender: UIButton) { changeServerValue(with: .increment) }
+
+ /// the decrement button was hit.
+ @IBAction func decrementButton(_ sender: UIButton) { changeServerValue(with: .decrement) }
+
+ // MARK: - Internal Helpers
+
+ /// Update the number on the server by a particular value. Note: the number passed in should only
+ /// be one above or below the current number.
+ private func changeServerValue(with type: Counter) {
+ let ref = Database.database().reference(withPath: Constants.databasePath)
+ // Update the current value of the number.
+ ref.runTransactionBlock { (currentData) -> TransactionResult in
+ guard let value = currentData.value as? Int else {
+ return TransactionResult.abort()
+ }
+
+ currentData.value = value + type.intValue
+ return TransactionResult.success(withValue: currentData)
+ }
+ }
+
+ // MARK: - View Controller Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Observe the current value, and update the UI every time it changes.
+ let ref = Database.database().reference(withPath: Constants.databasePath)
+
+ ref.observe(.value) { [weak self] (snapshot) in
+ guard let value = snapshot.value as? Int else {
+ print("Error grabbing value from Snapshot!")
+ return
+ }
+
+ self?.currentValue.text = "\(value)"
+ }
+ }
+
+ // MARK: - Constants
+
+ private struct Constants {
+ static let databasePath = "magicSyncingCounter"
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/EmailLoginViewController.swift b/Example/tvOSSample/tvOSSample/EmailLoginViewController.swift
new file mode 100644
index 0000000..60dfc43
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/EmailLoginViewController.swift
@@ -0,0 +1,93 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import UIKit
+import FirebaseAuth
+
+protocol EmailLoginDelegate {
+ func emailLogin(_ controller: EmailLoginViewController, signedInAs user: User)
+ func emailLogin(_ controller: EmailLoginViewController, failedWithError error: Error)
+}
+class EmailLoginViewController: UIViewController {
+
+ // MARK: - Public Properties
+
+ var delegate: EmailLoginDelegate?
+
+ // MARK: - User Interface
+
+ @IBOutlet private weak var emailAddress: UITextField!
+ @IBOutlet private weak var password: UITextField!
+
+ // MARK: - User Actions
+
+ @IBAction func logInButtonHit(_ sender: UIButton) {
+ guard let (email, password) = validatedInputs() else { return }
+
+ Auth.auth().signIn(withEmail: email, password: password) { [unowned self] (user, error) in
+ guard let user = user else {
+ print("Error signing in: \(error!)")
+ self.delegate?.emailLogin(self, failedWithError: error!)
+ return
+ }
+
+ print("Signed in as user: \(user.uid)!")
+ self.delegate?.emailLogin(self, signedInAs: user)
+ }
+ }
+
+ @IBAction func signUpButtonHit(_ sender: UIButton) {
+ guard let (email, password) = validatedInputs() else { return }
+
+ Auth.auth().createUser(withEmail: email, password: password) { [unowned self] (user, error) in
+ guard let user = user else {
+ print("Error signing up: \(error!)")
+ self.delegate?.emailLogin(self, failedWithError: error!)
+ return
+ }
+
+ print("Created new user: \(user.uid)!")
+ self.delegate?.emailLogin(self, signedInAs: user)
+ }
+ }
+
+ // MARK: - View Controller Lifecycle
+
+ override func viewDidLoad() {
+ }
+
+ // MARK: - Helper Methods
+
+ /// Validate the inputs for user email and password, returning the username and password if valid,
+ /// otherwise nil.
+ private func validatedInputs() -> (email: String, password: String)? {
+ guard let userEmail = emailAddress.text, userEmail.count >= 6 else {
+ presentError(with: "Email address isn't long enough.")
+ return nil
+ }
+
+ guard let userPassword = password.text, userPassword.count >= 6 else {
+ presentError(with: "Password is not long enough!")
+ return nil
+ }
+
+ return (userEmail, userPassword)
+ }
+
+ func presentError(with text: String) {
+ let alert = UIAlertController(title: "Error", message: text, preferredStyle: .alert)
+ alert.addAction(UIAlertAction(title: "Okay", style: .default))
+ present(alert, animated: true)
+ }
+}
diff --git a/Example/tvOSSample/tvOSSample/Info.plist b/Example/tvOSSample/tvOSSample/Info.plist
new file mode 100644
index 0000000..02942a3
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIUserInterfaceStyle</key>
+ <string>Automatic</string>
+</dict>
+</plist>
diff --git a/Example/tvOSSample/tvOSSample/StorageViewController.swift b/Example/tvOSSample/tvOSSample/StorageViewController.swift
new file mode 100644
index 0000000..4416649
--- /dev/null
+++ b/Example/tvOSSample/tvOSSample/StorageViewController.swift
@@ -0,0 +1,148 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import UIKit
+import FirebaseStorage
+
+class StorageViewController: UIViewController {
+ /// An enum describing the different states of the view controller.
+ private enum UIState: Equatable {
+ /// No image is being shown, waiting on user action.
+ case cleared
+
+ /// Currently downloading from Firebase.
+ case downloading(StorageTask)
+
+ /// The image has downloaded and should be displayed.
+ case downloaded(UIImage)
+
+ /// Show an error message and stop downloading.
+ case failed(String)
+
+ /// Equatable support for UIState.
+ static func ==(lhs: StorageViewController.UIState, rhs: StorageViewController.UIState) -> Bool {
+ switch (lhs, rhs) {
+ case (.cleared, .cleared): return true
+ case (.downloading, .downloading): return true
+ case (.downloaded, .downloaded): return true
+ case (.failed, .failed): return true
+ default: return false
+ }
+ }
+ }
+
+ /// MARK: - Properties
+
+ /// The current internal state of the view controller.
+ private var state: UIState = .cleared {
+ didSet { changeState(from: oldValue, to: state) }
+ }
+
+ // MARK: Interface
+
+ /// Image view to display the downloaded image.
+ @IBOutlet weak var imageView: UIImageView!
+
+ /// The download button.
+ @IBOutlet weak var downloadButton: UIButton!
+
+ /// The clear button.
+ @IBOutlet weak var clearButton: UIButton!
+
+ /// A visual representation of the state.
+ @IBOutlet weak var stateLabel: UILabel!
+
+ // MARK: - User Actions
+
+ @IBAction func downloadButtonHit(_ sender: UIButton) {
+ guard case .cleared = state else { return }
+
+ // Start the download.
+ let storage = Storage.storage()
+ let ref = storage.reference(withPath: Constants.downloadPath)
+ // TODO: Show progress bar here using proper API.
+ let task = ref.getData(maxSize: Constants.maxSize) { [unowned self] (data, error) in
+ guard let data = data else {
+ self.state = .failed("Error downloading: \(error!.localizedDescription)")
+ return
+ }
+
+ // Create a UIImage from the PNG data.
+ guard let image = UIImage(data: data) else {
+ self.state = .failed("Unable to initialize image with data downloaded.")
+ return
+ }
+
+ self.state = .downloaded(image)
+ }
+
+ // The completion block above could be run before this line in some situations. If that's the
+ // case, we don't need to do anything else and can return.
+ if case .downloaded = state { return }
+
+ // Set the state to downloading!
+ state = .downloading(task)
+ }
+
+ @IBAction func clearButtonHit(_ sender: UIButton) {
+ guard case .downloaded = state else { return }
+
+ state = .cleared
+ }
+
+ // MARK: - State Management
+
+ /// Changing from old state to new state.
+ private func changeState(from oldState: UIState, to newState: UIState) {
+ if oldState == newState { return }
+
+ switch (oldState, newState) {
+ // Regular state, start downloading the image.
+ case (.cleared, .downloading(_)):
+ // TODO: Update the UI with a spinner? Progress update?
+ stateLabel.text = "State: Downloading..."
+
+ // Download complete, ensure the download button is still off and enable the clear button.
+ case (_, .downloaded(let image)):
+ imageView.image = image
+ stateLabel.text = "State: Image downloaded!"
+
+ // Clear everything and reset to the original state.
+ case (_, .cleared):
+ imageView.image = nil
+ stateLabel.text = "State: Pending download"
+
+ // An error occurred.
+ case (_, .failed(let error)):
+ stateLabel.text = "State: \(error)"
+
+ // For now, as the default, throw a fatal error because it's an unexpected state. This will
+ // allow us to catch it immediately and add the required action or fix the bug.
+ default:
+ fatalError("Programmer error! Tried to go from \(oldState) to \(newState)")
+ }
+ }
+
+ // MARK: - Constants
+
+ /// Internal constants for this class.
+ private struct Constants {
+ /// The image name to download. Can comment this out and replace it with the other below it as
+ /// part of the demo. Ensure that Storage has an image uploaded to this path for this to
+ /// function properly.
+ static let downloadPath = "YOUR_IMAGE_NAME.jpg"
+
+ static let maxSize: Int64 = 1024 * 1024 * 10 // ~10MB
+ }
+}