diff options
Diffstat (limited to 'Example/Auth/SwiftSample')
-rw-r--r-- | Example/Auth/SwiftSample/AppDelegate.swift | 62 | ||||
-rw-r--r-- | Example/Auth/SwiftSample/AuthCredentialsTemplate.swift | 42 | ||||
-rw-r--r-- | Example/Auth/SwiftSample/InfoTemplate.plist | 79 | ||||
-rw-r--r-- | Example/Auth/SwiftSample/LaunchScreen.storyboard | 27 | ||||
-rw-r--r-- | Example/Auth/SwiftSample/Main.storyboard | 227 | ||||
-rw-r--r-- | Example/Auth/SwiftSample/Sample.entitlements | 10 | ||||
-rw-r--r-- | Example/Auth/SwiftSample/Stubs.swift | 45 | ||||
-rw-r--r-- | Example/Auth/SwiftSample/ViewController.swift | 700 |
8 files changed, 1192 insertions, 0 deletions
diff --git a/Example/Auth/SwiftSample/AppDelegate.swift b/Example/Auth/SwiftSample/AppDelegate.swift new file mode 100644 index 0000000..579385c --- /dev/null +++ b/Example/Auth/SwiftSample/AppDelegate.swift @@ -0,0 +1,62 @@ +/* + * 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 FirebaseCommunity.FirebaseCore +import GoogleSignIn + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + /** @var kGoogleClientID + @brief The Google client ID. + */ + private let kGoogleClientID = AuthCredentials.GOOGLE_CLIENT_ID + + // TODO: add Facebook login support as well. + + /** @var kFacebookAppID + @brief The Facebook app ID. + */ + private let kFacebookAppID = AuthCredentials.FACEBOOK_APP_ID + + /** @var window + @brief The main window of the app. + */ + var window: UIWindow? + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + FirebaseApp.configure() + GIDSignIn.sharedInstance().clientID = kGoogleClientID + return true + } + + @available(iOS 9.0, *) + func application(_ application: UIApplication, open url: URL, + options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool { + return GIDSignIn.sharedInstance().handle(url, + sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String, + annotation: nil) + } + + func application(_ application: UIApplication, open url: URL, sourceApplication: String?, + annotation: Any) -> Bool { + return GIDSignIn.sharedInstance().handle(url, sourceApplication: sourceApplication, + annotation: annotation) + } +} diff --git a/Example/Auth/SwiftSample/AuthCredentialsTemplate.swift b/Example/Auth/SwiftSample/AuthCredentialsTemplate.swift new file mode 100644 index 0000000..eea9335 --- /dev/null +++ b/Example/Auth/SwiftSample/AuthCredentialsTemplate.swift @@ -0,0 +1,42 @@ +/* + * 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. + */ + + +/* + Some of the Auth Credentials needs to be populated for the Sample build to work. + + Please follow the following steps to populate the valid AuthCredentials + and copy it to AuthCredentials.swift file + + You will need to replace the following values: + $KGOOGLE_CLIENT_ID + Get the value of the CLIENT_ID key in the GoogleService-Info.plist file. + + $KFACEBOOK_APP_ID + FACEBOOK_APP_ID is the developer's Facebook app's ID, to be used to test the + 'Signing in with Facebook' feature of Firebase Auth. Follow the instructions + on the Facebook developer site: https://developers.facebook.com/docs/apps/register + to obtain the id + + */ + +import Foundation + + +struct AuthCredentials { + static let FACEBOOK_APP_ID = "$KFACEBOOK_APP_ID" + static let GOOGLE_CLIENT_ID = "$KGOOGLE_CLIENT_ID" +} diff --git a/Example/Auth/SwiftSample/InfoTemplate.plist b/Example/Auth/SwiftSample/InfoTemplate.plist new file mode 100644 index 0000000..e51622b --- /dev/null +++ b/Example/Auth/SwiftSample/InfoTemplate.plist @@ -0,0 +1,79 @@ +<!-- + For this to be a valid plist file replace the following + $REVERSE_CLIENT_ID: + Value of REVERSED_CLIENT_ID key in the GoogleService-Info.plist file. +--> +<?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>en</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>CFBundleSignature</key> + <string>????</string> + <key>CFBundleURLTypes</key> + <array> + <dict> + <key>CFBundleTypeRole</key> + <string>Editor</string> + <key>CFBundleURLName</key> + <string>com.google.swiftbear</string> + <key>CFBundleURLSchemes</key> + <array> + <string>com.google.swiftbear</string> + </array> + </dict> + <dict> + <key>CFBundleTypeRole</key> + <string>Editor</string> + <key>CFBundleURLName</key> + <string>$REVERSE_CLIENT_ID</string> + <key>CFBundleURLSchemes</key> + <array> + <string>$REVERSE_CLIENT_ID</string> + </array> + </dict> + </array> + <key>CFBundleVersion</key> + <string>1</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UIMainStoryboardFile</key> + <string>Main</string> + <key>UIRequiredDeviceCapabilities</key> + <array> + <string>armv7</string> + </array> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>UISupportedInterfaceOrientations~ipad</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>LSApplicationQueriesSchemes</key> + <array> + <string>fbauth2</string> + </array> +</dict> +</plist> diff --git a/Example/Auth/SwiftSample/LaunchScreen.storyboard b/Example/Auth/SwiftSample/LaunchScreen.storyboard new file mode 100644 index 0000000..8326657 --- /dev/null +++ b/Example/Auth/SwiftSample/LaunchScreen.storyboard @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM"> + <dependencies> + <deployment identifier="iOS"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="EHf-IW-A2E"> + <objects> + <viewController id="01J-lp-oVM" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/> + <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> + <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + </view> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="53" y="375"/> + </scene> + </scenes> +</document> diff --git a/Example/Auth/SwiftSample/Main.storyboard b/Example/Auth/SwiftSample/Main.storyboard new file mode 100644 index 0000000..3525a8b --- /dev/null +++ b/Example/Auth/SwiftSample/Main.storyboard @@ -0,0 +1,227 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r"> + <device id="retina4_7" orientation="portrait"> + <adaptation id="fullscreen"/> + </device> + <dependencies> + <deployment identifier="iOS"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/> + <capability name="Constraints to layout margins" minToolsVersion="6.0"/> + <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="tne-QT-ifu"> + <objects> + <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SwiftSample" 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="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6HK-es-gyf"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <subviews> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Phone" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6m-cS-hAh"> + <rect key="frame" x="185" y="426" width="164" height="30"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sTr-Hf-KVD"> + <rect key="frame" x="185" y="366" width="164" height="30"/> + <fontDescription key="fontDescription" type="system" pointSize="20"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> + <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="zIt-Mq-gk3"> + <rect key="frame" x="185" y="336" width="164" height="30"/> + <nil key="textColor"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <textInputTraits key="textInputTraits"/> + </textField> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ria-OG-Nbj"> + <rect key="frame" x="105" y="98" width="264" height="25"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ObY-8p-vcQ"> + <rect key="frame" x="105" y="48" width="264" height="25"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QOn-3b-keq"> + <rect key="frame" x="99" y="131" width="250" height="167"/> + <connections> + <outlet property="dataSource" destination="BYZ-38-t0r" id="2Mt-EE-jxl"/> + <outlet property="delegate" destination="BYZ-38-t0r" id="qCy-Bt-yNL"/> + </connections> + </pickerView> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cWW-O4-BEO"> + <rect key="frame" x="105" y="23" width="264" height="25"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="31t-pr-Jxe"> + <rect key="frame" x="121" y="565" width="132" height="53"/> + <constraints> + <constraint firstAttribute="width" constant="132" id="Qvn-OR-BdF"/> + <constraint firstAttribute="height" constant="53" id="ST0-u0-dQ9"/> + </constraints> + <fontDescription key="fontDescription" type="system" pointSize="24"/> + <state key="normal" title="Execute"/> + <connections> + <action selector="execute:" destination="BYZ-38-t0r" eventType="touchUpInside" id="wnf-4v-z5f"/> + </connections> + </button> + <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="D0S-Sb-YNu"> + <rect key="frame" x="5" y="23" width="100" height="100"/> + <constraints> + <constraint firstAttribute="height" constant="100" id="3vv-V5-uVt"/> + <constraint firstAttribute="width" constant="100" id="Co2-YK-P0d"/> + </constraints> + </imageView> + <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cpy-NT-JJH"> + <rect key="frame" x="5" y="131" width="86" height="167"/> + <constraints> + <constraint firstAttribute="height" constant="167" id="3Pc-9v-A6F"/> + <constraint firstAttribute="width" constant="86" id="l7M-4A-dOO"/> + </constraints> + <connections> + <outlet property="dataSource" destination="BYZ-38-t0r" id="of7-wI-QNH"/> + <outlet property="delegate" destination="BYZ-38-t0r" id="Pfb-UW-zEF"/> + </connections> + </pickerView> + <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="gB8-2Z-VH6"> + <rect key="frame" x="185" y="454" width="164" height="30"/> + <nil key="textColor"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <textInputTraits key="textInputTraits" keyboardType="phonePad"/> + </textField> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Email" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EQe-sz-7yJ"> + <rect key="frame" x="185" y="306" width="164" height="30"/> + <constraints> + <constraint firstAttribute="width" constant="164" id="rEx-eC-W8w"/> + </constraints> + <fontDescription key="fontDescription" type="system" pointSize="20"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> + <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QFf-6f-e46"> + <rect key="frame" x="5" y="306" width="164" height="216"/> + <constraints> + <constraint firstAttribute="width" constant="164" id="T6v-LA-XU8"/> + </constraints> + <connections> + <outlet property="dataSource" destination="BYZ-38-t0r" id="ZEt-kl-Eml"/> + <outlet property="delegate" destination="BYZ-38-t0r" id="Bl0-Bi-SdS"/> + </connections> + </pickerView> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZxB-fU-vRm"> + <rect key="frame" x="105" y="73" width="264" height="25"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="sTA-Ti-DZm"> + <rect key="frame" x="185" y="396" width="164" height="30"/> + <nil key="textColor"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <textInputTraits key="textInputTraits"/> + </textField> + </subviews> + <constraints> + <constraint firstItem="ObY-8p-vcQ" firstAttribute="centerX" secondItem="cWW-O4-BEO" secondAttribute="centerX" id="0ib-aj-WX2"/> + <constraint firstItem="sTr-Hf-KVD" firstAttribute="width" secondItem="sTA-Ti-DZm" secondAttribute="width" id="2D7-Mj-W0C"/> + <constraint firstItem="zIt-Mq-gk3" firstAttribute="leading" secondItem="sTr-Hf-KVD" secondAttribute="leading" id="2NF-el-Jrp"/> + <constraint firstItem="sTA-Ti-DZm" firstAttribute="top" secondItem="sTr-Hf-KVD" secondAttribute="bottom" id="5Pb-vj-i2t"/> + <constraint firstItem="zIt-Mq-gk3" firstAttribute="width" secondItem="sTr-Hf-KVD" secondAttribute="width" id="5cR-Eg-Mok"/> + <constraint firstItem="c6m-cS-hAh" firstAttribute="height" secondItem="gB8-2Z-VH6" secondAttribute="height" id="5jm-To-vnO"/> + <constraint firstItem="gB8-2Z-VH6" firstAttribute="top" secondItem="sTA-Ti-DZm" secondAttribute="bottom" constant="28" id="8X6-in-hde"/> + <constraint firstItem="sTA-Ti-DZm" firstAttribute="width" secondItem="c6m-cS-hAh" secondAttribute="width" id="9C4-yu-4ce"/> + <constraint firstItem="c6m-cS-hAh" firstAttribute="width" secondItem="gB8-2Z-VH6" secondAttribute="width" id="9HL-oU-ywU"/> + <constraint firstItem="EQe-sz-7yJ" firstAttribute="height" secondItem="zIt-Mq-gk3" secondAttribute="height" id="AvD-fF-buO"/> + <constraint firstItem="EQe-sz-7yJ" firstAttribute="leading" secondItem="zIt-Mq-gk3" secondAttribute="leading" id="Ava-E1-SS0"/> + <constraint firstItem="sTr-Hf-KVD" firstAttribute="leading" secondItem="sTA-Ti-DZm" secondAttribute="leading" id="BMW-DF-S5S"/> + <constraint firstItem="31t-pr-Jxe" firstAttribute="top" secondItem="QFf-6f-e46" secondAttribute="bottom" constant="43" id="Em1-jR-L4v"/> + <constraint firstItem="ZxB-fU-vRm" firstAttribute="top" secondItem="ObY-8p-vcQ" secondAttribute="bottom" id="F0f-ea-9iX"/> + <constraint firstItem="D0S-Sb-YNu" firstAttribute="leading" secondItem="cpy-NT-JJH" secondAttribute="leading" id="FUv-hM-9eb"/> + <constraint firstAttribute="trailing" secondItem="QOn-3b-keq" secondAttribute="trailing" constant="26" id="GWC-2P-5UF"/> + <constraint firstItem="EQe-sz-7yJ" firstAttribute="top" secondItem="QFf-6f-e46" secondAttribute="top" id="Gsq-E6-6wg"/> + <constraint firstItem="zIt-Mq-gk3" firstAttribute="top" secondItem="EQe-sz-7yJ" secondAttribute="bottom" id="HER-e5-sOc"/> + <constraint firstItem="EQe-sz-7yJ" firstAttribute="width" secondItem="zIt-Mq-gk3" secondAttribute="width" id="IWr-QC-GQL"/> + <constraint firstItem="QOn-3b-keq" firstAttribute="trailing" secondItem="EQe-sz-7yJ" secondAttribute="trailing" id="JWT-sJ-Xwy"/> + <constraint firstItem="QOn-3b-keq" firstAttribute="leading" secondItem="cpy-NT-JJH" secondAttribute="trailing" constant="8" id="QIa-72-bhH"/> + <constraint firstItem="c6m-cS-hAh" firstAttribute="centerX" secondItem="gB8-2Z-VH6" secondAttribute="centerX" id="QKW-7H-lfO"/> + <constraint firstItem="ria-OG-Nbj" firstAttribute="centerX" secondItem="ZxB-fU-vRm" secondAttribute="centerX" id="RGq-CF-6Ca"/> + <constraint firstItem="QFf-6f-e46" firstAttribute="top" secondItem="QOn-3b-keq" secondAttribute="bottom" constant="8" id="RGv-Ix-KP5"/> + <constraint firstItem="D0S-Sb-YNu" firstAttribute="top" secondItem="6HK-es-gyf" secondAttribute="topMargin" constant="15" id="S58-uf-nql"/> + <constraint firstItem="ria-OG-Nbj" firstAttribute="height" secondItem="ZxB-fU-vRm" secondAttribute="height" id="S8E-ed-GlT"/> + <constraint firstItem="sTA-Ti-DZm" firstAttribute="height" secondItem="c6m-cS-hAh" secondAttribute="height" id="UpF-XL-ZNe"/> + <constraint firstItem="QOn-3b-keq" firstAttribute="top" secondItem="D0S-Sb-YNu" secondAttribute="bottom" constant="8" id="VTf-55-gwC"/> + <constraint firstItem="c6m-cS-hAh" firstAttribute="top" secondItem="sTA-Ti-DZm" secondAttribute="bottom" id="VZF-fX-C3a"/> + <constraint firstItem="sTr-Hf-KVD" firstAttribute="top" secondItem="zIt-Mq-gk3" secondAttribute="bottom" id="Vm8-Fh-aXe"/> + <constraint firstItem="cWW-O4-BEO" firstAttribute="leading" secondItem="D0S-Sb-YNu" secondAttribute="trailing" id="YLB-S5-D80"/> + <constraint firstItem="ZxB-fU-vRm" firstAttribute="width" secondItem="ObY-8p-vcQ" secondAttribute="width" id="b1Y-MS-gng"/> + <constraint firstItem="sTr-Hf-KVD" firstAttribute="height" secondItem="sTA-Ti-DZm" secondAttribute="height" id="bNv-hH-Noc"/> + <constraint firstItem="cpy-NT-JJH" firstAttribute="centerY" secondItem="QOn-3b-keq" secondAttribute="centerY" id="cQ7-Bu-P8o"/> + <constraint firstAttribute="trailing" secondItem="cWW-O4-BEO" secondAttribute="trailing" constant="6" id="d3V-Nv-JAE"/> + <constraint firstItem="zIt-Mq-gk3" firstAttribute="height" secondItem="sTr-Hf-KVD" secondAttribute="height" id="eEX-vS-Ado"/> + <constraint firstItem="ria-OG-Nbj" firstAttribute="width" secondItem="ZxB-fU-vRm" secondAttribute="width" id="fqd-aO-UVU"/> + <constraint firstItem="ZxB-fU-vRm" firstAttribute="height" secondItem="ObY-8p-vcQ" secondAttribute="height" id="ghn-WL-o6u"/> + <constraint firstItem="ZxB-fU-vRm" firstAttribute="centerX" secondItem="ObY-8p-vcQ" secondAttribute="centerX" id="in5-Lv-vIw"/> + <constraint firstItem="cpy-NT-JJH" firstAttribute="leading" secondItem="QFf-6f-e46" secondAttribute="leading" id="kF3-HN-LbD"/> + <constraint firstItem="sTA-Ti-DZm" firstAttribute="centerX" secondItem="c6m-cS-hAh" secondAttribute="centerX" id="kiI-Xh-5xo"/> + <constraint firstItem="cWW-O4-BEO" firstAttribute="height" secondItem="D0S-Sb-YNu" secondAttribute="height" multiplier="0.25" id="mKj-fA-ORq"/> + <constraint firstItem="D0S-Sb-YNu" firstAttribute="leading" secondItem="6HK-es-gyf" secondAttribute="leading" constant="5" id="nBQ-cr-fzi"/> + <constraint firstItem="ria-OG-Nbj" firstAttribute="top" secondItem="ZxB-fU-vRm" secondAttribute="bottom" id="pa9-NP-Hfc"/> + <constraint firstItem="gB8-2Z-VH6" firstAttribute="top" secondItem="c6m-cS-hAh" secondAttribute="bottom" constant="-2" id="qyL-2s-Bk6"/> + <constraint firstItem="cWW-O4-BEO" firstAttribute="top" secondItem="D0S-Sb-YNu" secondAttribute="top" id="r4K-Xo-2Xj"/> + <constraint firstItem="ObY-8p-vcQ" firstAttribute="top" secondItem="cWW-O4-BEO" secondAttribute="bottom" id="sSS-aL-VRW"/> + <constraint firstItem="ObY-8p-vcQ" firstAttribute="width" secondItem="cWW-O4-BEO" secondAttribute="width" id="vme-8e-MMM"/> + <constraint firstItem="EQe-sz-7yJ" firstAttribute="leading" secondItem="QFf-6f-e46" secondAttribute="trailing" constant="16" id="wnz-FV-4IK"/> + <constraint firstAttribute="bottom" secondItem="31t-pr-Jxe" secondAttribute="bottom" constant="64" id="yf5-EU-fUm"/> + <constraint firstItem="31t-pr-Jxe" firstAttribute="centerX" secondItem="6HK-es-gyf" secondAttribute="centerX" id="ypm-hz-NtG"/> + <constraint firstItem="cpy-NT-JJH" firstAttribute="height" secondItem="QOn-3b-keq" secondAttribute="height" id="zEO-wC-OTJ"/> + <constraint firstItem="ObY-8p-vcQ" firstAttribute="height" secondItem="cWW-O4-BEO" secondAttribute="height" id="zgf-ji-Ou6"/> + </constraints> + </scrollView> + </subviews> + <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstItem="6HK-es-gyf" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="bTX-8p-mgN"/> + <constraint firstItem="6HK-es-gyf" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="cRU-eQ-2SG"/> + <constraint firstItem="6HK-es-gyf" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="top" id="lgq-Qc-p06"/> + <constraint firstAttribute="trailing" secondItem="6HK-es-gyf" secondAttribute="trailing" id="ugY-4G-3fl"/> + </constraints> + </view> + <connections> + <outlet property="actionPicker" destination="QOn-3b-keq" id="PFo-aS-Wld"/> + <outlet property="actionTypePicker" destination="cpy-NT-JJH" id="zve-OZ-Zfa"/> + <outlet property="credentialTypePicker" destination="QFf-6f-e46" id="JP6-KS-WlB"/> + <outlet property="displayNameLabel" destination="cWW-O4-BEO" id="7Yf-D1-gbW"/> + <outlet property="emailField" destination="zIt-Mq-gk3" id="3wb-SN-KxQ"/> + <outlet property="emailInputLabel" destination="EQe-sz-7yJ" id="ARJ-Sl-Hy5"/> + <outlet property="emailLabel" destination="ObY-8p-vcQ" id="7LH-QE-OZ3"/> + <outlet property="passwordField" destination="sTA-Ti-DZm" id="vGb-gH-1BI"/> + <outlet property="passwordInputLabel" destination="sTr-Hf-KVD" id="8uU-J5-Y2Z"/> + <outlet property="phoneField" destination="gB8-2Z-VH6" id="nUb-F7-u3i"/> + <outlet property="profileImage" destination="D0S-Sb-YNu" id="goh-E4-oQW"/> + <outlet property="providerListLabel" destination="ria-OG-Nbj" id="j0I-72-FH7"/> + <outlet property="scrollView" destination="6HK-es-gyf" id="lA9-BY-R9E"/> + <outlet property="userIDLabel" destination="ZxB-fU-vRm" id="eyR-59-0Os"/> + </connections> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="304.80000000000001" y="428.63568215892059"/> + </scene> + </scenes> +</document> diff --git a/Example/Auth/SwiftSample/Sample.entitlements b/Example/Auth/SwiftSample/Sample.entitlements new file mode 100644 index 0000000..9199dae --- /dev/null +++ b/Example/Auth/SwiftSample/Sample.entitlements @@ -0,0 +1,10 @@ +<?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>application-identifier</key> + <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string> + <key>aps-environment</key> + <string>development</string> +</dict> +</plist> diff --git a/Example/Auth/SwiftSample/Stubs.swift b/Example/Auth/SwiftSample/Stubs.swift new file mode 100644 index 0000000..6733c4d --- /dev/null +++ b/Example/Auth/SwiftSample/Stubs.swift @@ -0,0 +1,45 @@ +/* + * 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. + */ + +/// This file contains a collection of stub functions to verify the Swift syntax of Firebase Auth +/// APIs in Swift for those that are not already covered by other parts of the app. +/// These functions are never executed, but just for passing compilation. + +import FirebaseCommunity.FirebaseAuth + +func actionCodeSettingsStubs() { + let actionCodeSettings = ActionCodeSettings() + actionCodeSettings.url = URL(string: "http://some.url/path/") + actionCodeSettings.setIOSBundleID("some.bundle.id") + actionCodeSettings.setAndroidPackageName("some.package.name", installIfNotAvailable: true, + minimumVersion: nil) + let _: String? = actionCodeSettings.iOSBundleID + let _: String? = actionCodeSettings.androidPackageName + let _: Bool = actionCodeSettings.androidInstallIfNotAvailable + let _: String? = actionCodeSettings.androidMinimumVersion + Auth.auth().sendPasswordReset(withEmail: "nobody@nowhere.com", + actionCodeSettings: actionCodeSettings) { (error: Error?) -> () in + } + Auth.auth().currentUser?.sendEmailVerification(with: actionCodeSettings) { + (error: Error?) -> () in + } +} + +func languageStubs() { + let _: String? = Auth.auth().languageCode + Auth.auth().languageCode = "asdf" + Auth.auth().useAppLanguage() +} diff --git a/Example/Auth/SwiftSample/ViewController.swift b/Example/Auth/SwiftSample/ViewController.swift new file mode 100644 index 0000000..e90b727 --- /dev/null +++ b/Example/Auth/SwiftSample/ViewController.swift @@ -0,0 +1,700 @@ +/* + * 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 FirebaseCommunity.FirebaseAuth +import GoogleSignIn + +final class ViewController: UIViewController, UITextFieldDelegate, FIRAuthUIDelegate { + /// The profile image for the currently signed-in user. + @IBOutlet weak var profileImage: UIImageView! + + /// The display name for the currently signed-in user. + @IBOutlet weak var displayNameLabel: UILabel! + + /// The email for the currently signed-in user. + @IBOutlet weak var emailLabel: UILabel! + + /// The ID for the currently signed-in user. + @IBOutlet weak var userIDLabel: UILabel! + + /// The list of providers for the currently signed-in user. + @IBOutlet weak var providerListLabel: UILabel! + + /// The picker for the list of action types. + @IBOutlet weak var actionTypePicker: UIPickerView! + + /// The picker for the list of actions. + @IBOutlet weak var actionPicker: UIPickerView! + + /// The picker for the list of credential types. + @IBOutlet weak var credentialTypePicker: UIPickerView! + + /// The label for the "email" text field. + @IBOutlet weak var emailInputLabel: UILabel! + + /// The "email" text field. + @IBOutlet weak var emailField: UITextField! + + /// The label for the "password" text field. + @IBOutlet weak var passwordInputLabel: UILabel! + + /// The "password" text field. + @IBOutlet weak var passwordField: UITextField! + + /// The "phone" text field. + @IBOutlet weak var phoneField: UITextField! + + /// The scroll view holding all content. + @IBOutlet weak var scrollView: UIScrollView! + + // The active keyboard input field. + var activeField: UITextField? + + /// The currently selected action type. + fileprivate var actionType = ActionType(rawValue: 0)! { + didSet { + if actionType != oldValue { + actionPicker.reloadAllComponents() + actionPicker.selectRow(actionType == .auth ? authAction.rawValue : userAction.rawValue, + inComponent: 0, animated: false) + } + } + } + + /// The currently selected auth action. + fileprivate var authAction = AuthAction(rawValue: 0)! + + /// The currently selected user action. + fileprivate var userAction = UserAction(rawValue: 0)! + + /// The currently selected credential. + fileprivate var credentialType = CredentialType(rawValue: 0)! + + /// The current Firebase user. + fileprivate var user: User? = nil { + didSet { + if user?.uid != oldValue?.uid { + actionTypePicker.reloadAllComponents() + actionType = ActionType(rawValue: actionTypePicker.selectedRow(inComponent: 0))! + } + } + } + + func registerForKeyboardNotifications() { + NotificationCenter.default.addObserver(self, + selector: + #selector(keyboardWillBeShown(notification:)), + name: NSNotification.Name.UIKeyboardWillShow, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillBeHidden(notification:)), + name: NSNotification.Name.UIKeyboardWillHide, + object: nil) + } + + func deregisterFromKeyboardNotifications() { + NotificationCenter.default.removeObserver(self, + name: NSNotification.Name.UIKeyboardWillShow, + object: nil) + NotificationCenter.default.removeObserver(self, + name: NSNotification.Name.UIKeyboardWillHide, + object: nil) + } + + func keyboardWillBeShown(notification: NSNotification) { + scrollView.isScrollEnabled = true + let info = notification.userInfo! + let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size + let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0) + + scrollView.contentInset = contentInsets + scrollView.scrollIndicatorInsets = contentInsets + + var aRect = self.view.frame + aRect.size.height -= keyboardSize!.height + if let activeField = activeField { + if (!aRect.contains(activeField.frame.origin)) { + scrollView.scrollRectToVisible(activeField.frame, animated: true) + } + } + } + + func keyboardWillBeHidden(notification: NSNotification){ + let info = notification.userInfo! + let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size + let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0) + scrollView.contentInset = contentInsets + scrollView.scrollIndicatorInsets = contentInsets + self.view.endEditing(true) + scrollView.isScrollEnabled = false + } + + func textFieldDidBeginEditing(_ textField: UITextField) { + activeField = textField + } + + func textFieldDidEndEditing(_ textField: UITextField) { + activeField = nil + } + + func dismissKeyboard() { + view.endEditing(true) + } + + func verify(phoneNumber: String, completion: @escaping (PhoneAuthCredential?, Error?) -> Void) { + if #available(iOS 8.0, *) { + PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate:self) { + verificationID, error in + guard error == nil else { + completion(nil, error) + return + } + let codeAlertController = + UIAlertController(title: "Enter Code", message: nil, preferredStyle: .alert) + codeAlertController.addTextField { textfield in + textfield.placeholder = "SMS Code" + textfield.keyboardType = UIKeyboardType.numberPad + } + codeAlertController.addAction(UIAlertAction(title: "OK", + style: .default, + handler: { (UIAlertAction) in + let code = codeAlertController.textFields!.first!.text! + let phoneCredential = + PhoneAuthProvider.provider().credential(withVerificationID: verificationID ?? "", + verificationCode: code) + completion(phoneCredential, nil) + })) + self.present(codeAlertController, animated: true, completion: nil) + } + } + } + /// The user's photo URL used by the last network request for its contents. + fileprivate var lastPhotoURL: URL? = nil + + override func viewDidLoad() { + GIDSignIn.sharedInstance().uiDelegate = self + updateUserInfo(Auth.auth()) + NotificationCenter.default.addObserver(forName: .AuthStateDidChange, + object: Auth.auth(), queue: nil) { notification in + self.updateUserInfo(notification.object as? Auth) + } + phoneField.delegate = self + registerForKeyboardNotifications() + + let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + scrollView.addGestureRecognizer(tap) + } + + override func viewWillDisappear(_ animated: Bool) { + deregisterFromKeyboardNotifications() + } + + /// Executes the action designated by the operator on the UI. + @IBAction func execute(_ sender: UIButton) { + switch actionType { + case .auth: + switch authAction { + case .fetchProviderForEmail: + Auth.auth().fetchProviders(forEmail: emailField.text!) { providers, error in + self.ifNoError(error) { + self.showAlert(title: "Providers", message: providers?.joined(separator: ", ")) + } + } + case .signInAnonymously: + Auth.auth().signInAnonymously() { user, error in + self.ifNoError(error) { + self.showAlert(title: "Signed In Anonymously") + } + } + case .signInWithCredential: + getCredential() { credential in + Auth.auth().signIn(with: credential) { user, error in + self.ifNoError(error) { + self.showAlert(title: "Signed In With Credential", message: user?.textDescription) + } + } + } + case .createUser: + Auth.auth().createUser(withEmail: emailField.text!, password: passwordField.text!) { + user, error in + self.ifNoError(error) { + self.showAlert(title: "Signed In With Credential", message: user?.textDescription) + } + } + case .signOut: + try! Auth.auth().signOut() + GIDSignIn.sharedInstance().signOut() + } + case .user: + switch userAction { + case .updateEmail: + user!.updateEmail(to: emailField.text!) { error in + self.ifNoError(error) { + self.showAlert(title: "Updated Email", message: self.user?.email) + } + } + case .updatePhone: + let phoneNumber = phoneField.text + self.verify(phoneNumber: phoneNumber!, completion: { (phoneAuthCredential, error) in + guard error == nil else { + self.showAlert(title: "Error", message: error!.localizedDescription) + return + } + self.user!.updatePhoneNumber(phoneAuthCredential!, completion: { error in + self.ifNoError(error) { + self.showAlert(title: "Updated Phone Number") + self.updateUserInfo(Auth.auth()) + } + }) + }) + case .updatePassword: + user!.updatePassword(to: passwordField.text!) { error in + self.ifNoError(error) { + self.showAlert(title: "Updated Password") + } + } + case .reload: + user!.reload() { error in + self.ifNoError(error) { + self.showAlert(title: "Reloaded", message: self.user?.textDescription) + } + } + case .reauthenticate: + getCredential() { credential in + self.user!.reauthenticate(with: credential) { error in + self.ifNoError(error) { + self.showAlert(title: "Reauthenticated", message: self.user?.textDescription) + } + } + } + case .getToken: + user!.getIDToken() { token, error in + self.ifNoError(error) { + self.showAlert(title: "Got ID Token", message: token) + } + } + case .linkWithCredential: + getCredential() { credential in + self.user!.link(with: credential) { user, error in + self.ifNoError(error) { + self.showAlert(title: "Linked With Credential", message: user?.textDescription) + } + } + } + case .deleteAccount: + user!.delete() { error in + self.ifNoError(error) { + self.showAlert(title: "Deleted Account") + } + } + } + } + } + + /// Gets an AuthCredential potentially asynchronously. + private func getCredential(completion: @escaping (AuthCredential) -> Void) { + switch credentialType { + case .google: + GIDSignIn.sharedInstance().delegate = GoogleSignInDelegate(completion: { user, error in + self.ifNoError(error) { + completion(GoogleAuthProvider.credential( + withIDToken: user!.authentication.idToken, + accessToken: user!.authentication.accessToken)) + } + }) + GIDSignIn.sharedInstance().signIn() + case .password: + completion(EmailAuthProvider.credential(withEmail: emailField.text!, + password: passwordField.text!)) + case .phone: + let phoneNumber = phoneField.text + self.verify(phoneNumber: phoneNumber!, completion: { (phoneAuthCredential, error) in + guard error == nil else { + self.showAlert(title: "Error", message: error!.localizedDescription) + return + } + completion(phoneAuthCredential!) + }) + } + } + + /// Updates user's profile image and info text. + private func updateUserInfo(_ auth: Auth?) { + user = auth?.currentUser + displayNameLabel.text = user?.displayName + emailLabel.text = user?.email + userIDLabel.text = user?.uid + let providers = user?.providerData.map { userInfo in userInfo.providerID } + providerListLabel.text = providers?.joined(separator: ", ") + if let photoURL = user?.photoURL { + lastPhotoURL = photoURL + let queue: DispatchQueue + if #available(iOS 8.0, *) { + queue = DispatchQueue.global(qos: .background) + } else { + queue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background) + } + queue.async { + if let imageData = try? Data(contentsOf: photoURL) { + let image = UIImage(data: imageData) + DispatchQueue.main.async { + if self.lastPhotoURL == photoURL { + self.profileImage.image = image + } + } + } + } + } else { + lastPhotoURL = nil + self.profileImage.image = nil + } + updateControls() + } + + // Updates the states of the UI controls. + fileprivate func updateControls() { + let action: Action + switch actionType { + case .auth: + action = authAction + case .user: + action = userAction + } + let isCredentialEnabled = action.requiresCredential + credentialTypePicker.isUserInteractionEnabled = isCredentialEnabled + credentialTypePicker.alpha = isCredentialEnabled ? 1.0 : 0.6 + let isEmailEnabled = isCredentialEnabled && credentialType.requiresEmail || action.requiresEmail + emailInputLabel.alpha = isEmailEnabled ? 1.0 : 0.6 + emailField.isEnabled = isEmailEnabled + let isPasswordEnabled = isCredentialEnabled && credentialType.requiresPassword || + action.requiresPassword + passwordInputLabel.alpha = isPasswordEnabled ? 1.0 : 0.6 + passwordField.isEnabled = isPasswordEnabled + phoneField.isEnabled = credentialType.requiresPhone || action.requiresPhoneNumber + } + + fileprivate func showAlert(title: String, message: String? = "") { + if #available(iOS 8.0, *) { + let alertController = + UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", + style: .default, + handler: { (UIAlertAction) in + alertController.dismiss(animated: true, completion: nil) + })) + self.present(alertController, animated: true, completion: nil) + } else { + UIAlertView(title: title, + message: message ?? "(NULL)", + delegate: nil, + cancelButtonTitle: nil, + otherButtonTitles: "OK").show() + } + } + + private func ifNoError(_ error: Error?, execute: () -> Void) { + guard error == nil else { + showAlert(title: "Error", message: error!.localizedDescription) + return + } + execute() + } +} + +extension ViewController : GIDSignInUIDelegate { + func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) { + present(viewController, animated: true, completion: nil) + } + + func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) { + dismiss(animated: true, completion: nil) + } +} + +extension ViewController : UIPickerViewDataSource { + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + switch pickerView { + case actionTypePicker: + if Auth.auth().currentUser != nil { + return ActionType.countWithUser + } else { + return ActionType.countWithoutUser + } + case actionPicker: + switch actionType { + case .auth: + return AuthAction.count + case .user: + return UserAction.count + } + case credentialTypePicker: + return CredentialType.count + default: + return 0 + } + } +} + +extension ViewController : UIPickerViewDelegate { + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) + -> String? { + switch pickerView { + case actionTypePicker: + return ActionType(rawValue: row)!.text + case actionPicker: + switch actionType { + case .auth: + return AuthAction(rawValue: row)!.text + case .user: + return UserAction(rawValue: row)!.text + } + case credentialTypePicker: + return CredentialType(rawValue: row)!.text + default: + return nil + } + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + switch pickerView { + case actionTypePicker: + actionType = ActionType(rawValue: row)! + case actionPicker: + switch actionType { + case .auth: + authAction = AuthAction(rawValue: row)! + case .user: + userAction = UserAction(rawValue: row)! + } + case credentialTypePicker: + credentialType = CredentialType(rawValue: row)! + default: + break + } + updateControls() + } +} + +/// An adapter class to pass GoogleSignIn delegate method to a block. +fileprivate final class GoogleSignInDelegate: NSObject, GIDSignInDelegate { + + private let completion: (GIDGoogleUser?, Error?) -> Void + private var retainedSelf: GoogleSignInDelegate? + + init(completion: @escaping (GIDGoogleUser?, Error?) -> Void) { + self.completion = completion + super.init() + retainedSelf = self + } + + func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser?, withError error: Error?) { + completion(user, error) + retainedSelf = nil + } +} + +/// The list of all possible action types. +fileprivate enum ActionType: Int { + + case auth, user + + // Count of action types when no user is signed in. + static var countWithoutUser: Int { + return ActionType.auth.rawValue + 1 + } + + // Count of action types when a user is signed in. + static var countWithUser: Int { + return ActionType.user.rawValue + 1 + } + + /// The text description for a particular enum value. + var text : String { + switch self { + case .auth: + return "Auth" + case .user: + return "User" + } + } +} + +fileprivate protocol Action { + /// The text description for the particular action. + var text: String { get } + + /// Whether or not the action requires a credential. + var requiresCredential : Bool { get } + + /// Whether or not the action requires an email. + var requiresEmail: Bool { get } + + /// Whether or not the credential requires a password. + var requiresPassword: Bool { get } + + /// Whether or not the credential requires a phone number. + var requiresPhoneNumber: Bool { get } +} + +/// The list of all possible actions the operator can take on the Auth object. +fileprivate enum AuthAction: Int, Action { + + case fetchProviderForEmail, signInAnonymously, signInWithCredential, createUser, signOut + + /// Total number of auth actions. + static var count: Int { + return AuthAction.signOut.rawValue + 1 + } + + var text : String { + switch self { + case .fetchProviderForEmail: + return "Fetch Provider ⬇️" + case .signInAnonymously: + return "Sign In Anonymously" + case .signInWithCredential: + return "Sign In w/ Credential ↙️" + case .createUser: + return "Create User ⬇️" + case .signOut: + return "Sign Out" + } + } + + var requiresCredential : Bool { + return self == .signInWithCredential + } + + var requiresEmail : Bool { + return self == .fetchProviderForEmail || self == .createUser + } + + var requiresPassword : Bool { + return self == .createUser + } + + var requiresPhoneNumber: Bool { + return false + } +} + +/// The list of all possible actions the operator can take on the User object. +fileprivate enum UserAction: Int, Action { + + case updateEmail, updatePhone, updatePassword, reload, reauthenticate, getToken, + linkWithCredential, deleteAccount + + /// Total number of user actions. + static var count: Int { + return UserAction.deleteAccount.rawValue + 1 + } + + var text : String { + switch self { + case .updateEmail: + return "Update Email ⬇️" + case .updatePhone: + if #available(iOS 8.0, *) { + return "Update Phone ⬇️" + } else { + return "-" + } + case .updatePassword: + return "Update Password ⬇️" + case .reload: + return "Reload" + case .reauthenticate: + return "Reauthenticate ↙️" + case .getToken: + return "Get Token" + case .linkWithCredential: + return "Link With Credential ↙️" + case .deleteAccount: + return "Delete Account" + } + } + + var requiresCredential : Bool { + return self == .reauthenticate || self == .linkWithCredential + } + + var requiresEmail : Bool { + return self == .updateEmail + } + + var requiresPassword : Bool { + return self == .updatePassword + } + + var requiresPhoneNumber : Bool { + return self == .updatePhone + } + +} + +/// The list of all possible credential types the operator can use to sign in or link. +fileprivate enum CredentialType: Int { + + case google, password, phone + + /// Total number of enum values. + static var count: Int { + return CredentialType.phone.rawValue + 1 + } + + /// The text description for a particular enum value. + var text : String { + switch self { + case .google: + return "Google" + case .password: + return "Password ➡️️" + case .phone: + if #available(iOS 8.0, *) { + return "Phone ➡️️" + } else { + return "-" + } + } + } + + /// Whether or not the credential requires an email. + var requiresEmail : Bool { + return self == .password + } + + /// Whether or not the credential requires a password. + var requiresPassword : Bool { + return self == .password + } + + /// Whether or not the credential requires a phone number. + var requiresPhone : Bool { + return self == .phone + } +} + +fileprivate extension User { + var textDescription: String { + return self.displayName ?? self.email ?? self.uid + } +} |