├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── Mobile App Bootstrap.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Mobile App Bootstrap ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-50.png │ │ ├── Icon-50@2x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-small.png │ │ ├── Icon-small@2x.png │ │ ├── Icon-small@3x.png │ │ ├── Icon.png │ │ └── Icon@2x.png │ ├── Calculator.imageset │ │ ├── Contents.json │ │ ├── ios7-calculator_TabBar.png │ │ ├── ios7-calculator_TabBar@2x.png │ │ └── ios7-calculator_TabBar@3x.png │ ├── Contents.json │ ├── Dollar.imageset │ │ ├── Contents.json │ │ ├── social-usd_TabBar.png │ │ ├── social-usd_TabBar@2x.png │ │ └── social-usd_TabBar@3x.png │ ├── Home.imageset │ │ ├── Contents.json │ │ ├── ios7-home_TabBar.png │ │ ├── ios7-home_TabBar@2x.png │ │ └── ios7-home_TabBar@3x.png │ ├── LaunchGears.imageset │ │ ├── Contents.json │ │ ├── Gears.png │ │ └── Gears@2x.png │ └── Pulse.imageset │ │ ├── Contents.json │ │ ├── ios7-pulse_TabBar.png │ │ ├── ios7-pulse_TabBar@2x.png │ │ └── ios7-pulse_TabBar@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── HomeViewController.h ├── HomeViewController.m ├── Info.plist ├── Mobile App Bootstrap.entitlements ├── TokenManager.h ├── TokenManager.m ├── Viz1ViewController.h ├── Viz1ViewController.m ├── Viz2ViewController.h ├── Viz2ViewController.m ├── Viz3ViewController.h ├── Viz3ViewController.m ├── WebViewDelegate.h ├── WebViewDelegate.m ├── main.m └── www │ ├── css │ └── style.css │ ├── img │ ├── AngularLogo.png │ ├── Download.png │ ├── ExploreViz.png │ ├── HeaderLogo.png │ ├── HelpViz.png │ ├── JavaScriptLogo.png │ ├── StoriesViz.png │ ├── YAABA.jpg │ ├── adduser.png │ ├── bbq.jpg │ ├── calendar.png │ ├── finduser.png │ ├── history.png │ ├── stats.png │ └── videos.png │ ├── index.html │ └── lib │ ├── ion-datetime-picker │ ├── .bower.json │ ├── LICENSE │ ├── README.md │ ├── bower.json │ ├── gulpfile.js │ ├── package.json │ ├── release │ │ ├── ion-datetime-picker.min.css │ │ └── ion-datetime-picker.min.js │ └── src │ │ ├── picker-i18n.js │ │ ├── picker-popup.html │ │ ├── picker.js │ │ └── picker.scss │ └── ionic │ ├── css │ ├── ionic.css │ └── ionic.min.css │ ├── fonts │ ├── ionicons.eot │ ├── ionicons.svg │ ├── ionicons.ttf │ └── ionicons.woff │ ├── js │ ├── angular-ui │ │ ├── angular-ui-router.js │ │ └── angular-ui-router.min.js │ ├── angular │ │ ├── angular-animate.js │ │ ├── angular-animate.min.js │ │ ├── angular-resource.js │ │ ├── angular-resource.min.js │ │ ├── angular-sanitize.js │ │ ├── angular-sanitize.min.js │ │ ├── angular.js │ │ └── angular.min.js │ ├── ionic-angular.js │ ├── ionic-angular.min.js │ ├── ionic.bundle.js │ ├── ionic.bundle.min.js │ ├── ionic.js │ └── ionic.min.js │ ├── scss │ ├── _action-sheet.scss │ ├── _animations.scss │ ├── _backdrop.scss │ ├── _badge.scss │ ├── _bar.scss │ ├── _button-bar.scss │ ├── _button.scss │ ├── _checkbox.scss │ ├── _form.scss │ ├── _grid.scss │ ├── _items.scss │ ├── _list.scss │ ├── _loading.scss │ ├── _menu.scss │ ├── _mixins.scss │ ├── _modal.scss │ ├── _platform.scss │ ├── _popover.scss │ ├── _popup.scss │ ├── _progress.scss │ ├── _radio.scss │ ├── _range.scss │ ├── _refresher.scss │ ├── _reset.scss │ ├── _scaffolding.scss │ ├── _select.scss │ ├── _slide-box.scss │ ├── _slides.scss │ ├── _spinner.scss │ ├── _tabs.scss │ ├── _toggle.scss │ ├── _transitions.scss │ ├── _type.scss │ ├── _util.scss │ ├── _variables.scss │ ├── ionic.scss │ ├── ionicons │ │ ├── _ionicons-font.scss │ │ ├── _ionicons-icons.scss │ │ ├── _ionicons-variables.scss │ │ └── ionicons.scss │ └── tsconfig.json │ └── version.json ├── README.md ├── mobile-connected-client ├── README.md ├── plugin.xml ├── src │ └── ios │ │ ├── TableauOAuth.h │ │ ├── TableauOAuth.m │ │ ├── TableauOAuthCordova.h │ │ └── TableauOAuthCordova.m └── www │ └── TableauOAuth.js └── samples ├── BarcodeScanner ├── Barcode Scanner.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── Barcode Scanner.xcscmblueprint │ │ └── IDEWorkspaceChecks.plist ├── Barcode Scanner.xcworkspace │ └── contents.xcworkspacedata ├── Mobile App Bootstrap │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-40.png │ │ │ ├── Icon-40@2x.png │ │ │ ├── Icon-40@3x.png │ │ │ ├── Icon-50.png │ │ │ ├── Icon-50@2x.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-72.png │ │ │ ├── Icon-72@2x.png │ │ │ ├── Icon-76.png │ │ │ ├── Icon-76@2x.png │ │ │ ├── Icon-83.5@2x.png │ │ │ ├── Icon-small.png │ │ │ ├── Icon-small@2x.png │ │ │ ├── Icon-small@3x.png │ │ │ ├── Icon.png │ │ │ └── Icon@2x.png │ │ ├── Camera.imageset │ │ │ ├── Contents.json │ │ │ ├── camera.png │ │ │ ├── camera@2x.png │ │ │ └── camera@3x.png │ │ ├── Contents.json │ │ ├── LaunchGears.imageset │ │ │ ├── Contents.json │ │ │ ├── Gears.png │ │ │ └── Gears@2x.png │ │ └── Pulse.imageset │ │ │ ├── Contents.json │ │ │ ├── ios7-pulse_TabBar.png │ │ │ ├── ios7-pulse_TabBar@2x.png │ │ │ └── ios7-pulse_TabBar@3x.png │ ├── Barcode Scanner.entitlements │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── HomeViewController.h │ ├── HomeViewController.m │ ├── Info.plist │ ├── TokenManager.h │ ├── TokenManager.m │ ├── Viz1ViewController.h │ ├── Viz1ViewController.m │ ├── WebViewDelegate.h │ ├── WebViewDelegate.m │ └── main.m ├── README.md └── sample_barcodes.png └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | Mobile?App?Bootstrap/www/** linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "samples/BarcodeScanner/MTBBarcodeScanner"] 2 | path = samples/BarcodeScanner/MTBBarcodeScanner 3 | url = https://github.com/mikebuss/MTBBarcodeScanner.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tableau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Mobile App Bootstrap.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Mobile App Bootstrap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/20/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/20/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TokenManager.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | 22 | // Check keychain and inflate any cookies we've got stored from a previous session. 23 | // Note that this doesn't make much sense for Tableau Public, but does when connecting to your own server. 24 | [TokenManager loadCookiesFromStorageForServer:[NSURL URLWithString:@"https://public.tableau.com"]]; 25 | 26 | return YES; 27 | } 28 | 29 | 30 | - (void)applicationWillResignActive:(UIApplication *)application { 31 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 32 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 33 | } 34 | 35 | 36 | - (void)applicationDidEnterBackground:(UIApplication *)application { 37 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 39 | } 40 | 41 | 42 | - (void)applicationWillEnterForeground:(UIApplication *)application { 43 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 44 | } 45 | 46 | 47 | - (void)applicationDidBecomeActive:(UIApplication *)application { 48 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 49 | } 50 | 51 | 52 | - (void)applicationWillTerminate:(UIApplication *)application { 53 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-small.png", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-small@2x.png", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "size" : "29x29", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-small@3x.png", 29 | "scale" : "3x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@2x.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "size" : "40x40", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-40@3x.png", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "size" : "57x57", 45 | "idiom" : "iphone", 46 | "filename" : "Icon.png", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "size" : "57x57", 51 | "idiom" : "iphone", 52 | "filename" : "Icon@2x.png", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "size" : "60x60", 57 | "idiom" : "iphone", 58 | "filename" : "Icon-60@2x.png", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "size" : "60x60", 63 | "idiom" : "iphone", 64 | "filename" : "Icon-60@3x.png", 65 | "scale" : "3x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "20x20", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "20x20", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "idiom" : "ipad", 79 | "size" : "29x29", 80 | "scale" : "1x" 81 | }, 82 | { 83 | "idiom" : "ipad", 84 | "size" : "29x29", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-40.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-40@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "50x50", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-50.png", 103 | "scale" : "1x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-50@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "72x72", 113 | "idiom" : "ipad", 114 | "filename" : "Icon-72.png", 115 | "scale" : "1x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "Icon-72@2x.png", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "size" : "76x76", 125 | "idiom" : "ipad", 126 | "filename" : "Icon-76.png", 127 | "scale" : "1x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "Icon-76@2x.png", 133 | "scale" : "2x" 134 | }, 135 | { 136 | "size" : "83.5x83.5", 137 | "idiom" : "ipad", 138 | "filename" : "Icon-83.5@2x.png", 139 | "scale" : "2x" 140 | } 141 | ], 142 | "info" : { 143 | "version" : 1, 144 | "author" : "xcode" 145 | } 146 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@3x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Calculator.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ios7-calculator_TabBar.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ios7-calculator_TabBar@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ios7-calculator_TabBar@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Calculator.imageset/ios7-calculator_TabBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Calculator.imageset/ios7-calculator_TabBar.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Calculator.imageset/ios7-calculator_TabBar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Calculator.imageset/ios7-calculator_TabBar@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Calculator.imageset/ios7-calculator_TabBar@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Calculator.imageset/ios7-calculator_TabBar@3x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Dollar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "social-usd_TabBar.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "social-usd_TabBar@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "social-usd_TabBar@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Dollar.imageset/social-usd_TabBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Dollar.imageset/social-usd_TabBar.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Dollar.imageset/social-usd_TabBar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Dollar.imageset/social-usd_TabBar@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Dollar.imageset/social-usd_TabBar@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Dollar.imageset/social-usd_TabBar@3x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ios7-home_TabBar.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ios7-home_TabBar@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ios7-home_TabBar@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Home.imageset/ios7-home_TabBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Home.imageset/ios7-home_TabBar.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Home.imageset/ios7-home_TabBar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Home.imageset/ios7-home_TabBar@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Home.imageset/ios7-home_TabBar@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Home.imageset/ios7-home_TabBar@3x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Gears.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Gears@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ios7-pulse_TabBar.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ios7-pulse_TabBar@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ios7-pulse_TabBar@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@2x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@3x.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/HomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HomeViewController : UIViewController 12 | 13 | @property (strong, nonatomic) IBOutlet WKWebView *webView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/HomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | 11 | @implementation HomeViewController 12 | 13 | - (void)viewDidLoad { 14 | [super viewDidLoad]; 15 | // Do any additional setup after loading the view, typically from a nib. 16 | 17 | // Load index.html from the www directory. 18 | NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:@"www"]]; 19 | 20 | [self.webView loadRequest:[NSURLRequest requestWithURL:url]]; 21 | } 22 | 23 | 24 | - (void)didReceiveMemoryWarning { 25 | [super didReceiveMemoryWarning]; 26 | // Dispose of any resources that can be recreated. 27 | } 28 | 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarTintParameters 32 | 33 | UINavigationBar 34 | 35 | Style 36 | UIBarStyleDefault 37 | Translucent 38 | 39 | 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | NSAppTransportSecurity 55 | 56 | NSAllowsArbitraryLoads 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Mobile App Bootstrap.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/TokenManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TokenManager.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface TokenManager : NSObject 13 | 14 | + (void)loadCookiesFromStorageForServer: (nonnull NSURL *) serverUrl ; 15 | + (void)createTokensIfNeeded: (nonnull NSURL *) serverUrl; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/TokenManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TokenManager.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "TokenManager.h" 10 | #import "TableauOAuth.h" 11 | 12 | @implementation TokenManager 13 | 14 | 15 | static TableauOAuth *_auth; 16 | 17 | + (void)initialize { 18 | if (self == [TokenManager class]) { 19 | _auth = [[TableauOAuth alloc] init]; 20 | } 21 | } 22 | 23 | + (void)loadCookiesFromStorageForServer: (nonnull NSURL *) serverUrl { 24 | [_auth loadCookiesFromStorageForServer: serverUrl]; 25 | } 26 | 27 | + (void)createTokensIfNeeded: (nonnull NSURL *) serverUrl { 28 | [_auth requestTokensForSession:serverUrl]; 29 | } 30 | 31 | 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Viz1ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // Viz1ViewController.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Viz1ViewController : UIViewController 12 | 13 | @property (strong, nonatomic) IBOutlet WKWebView *webView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Viz1ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // Viz1ViewController.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "Viz1ViewController.h" 10 | #import "WebViewDelegate.h" 11 | 12 | @implementation Viz1ViewController { 13 | WebViewDelegate *webViewDelegate; 14 | } 15 | 16 | - (void)viewDidLoad { 17 | [super viewDidLoad]; 18 | // Do any additional setup after loading the view, typically from a nib. 19 | 20 | // Parameters for passing to the viz: 21 | // http://onlinehelp.tableau.com/current/server/en-us/embed_list.htm 22 | // :embed=y hides the top nav area 23 | // :tooltip=n hides tooltips. Mainly because "View Data" in a tooltip navigates to a new page, and our sample-code webviews don't have a back button. (Yet!) 24 | // :toolbar=n hides the toolbar. The toolbar is cool, but it has some functionality that this sample code doesn't handle. 25 | // :showVizHome=no is to remove the top chrome on Public 26 | 27 | NSURL *url = [NSURL URLWithString:@"https://public.tableau.com/views/10_0InternationalTourism/InternationalTourism?:embed=y&:tooltip=n&:toolbar=n&:showVizHome=no"]; 28 | 29 | webViewDelegate = [[WebViewDelegate alloc] init]; 30 | self.webView.navigationDelegate = webViewDelegate; 31 | 32 | [self.webView loadRequest:[NSURLRequest requestWithURL: url]]; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Viz2ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // Viz2ViewController.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Viz2ViewController : UIViewController 12 | 13 | @property (strong, nonatomic) IBOutlet WKWebView *webView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Viz2ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // Viz2ViewController.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "Viz2ViewController.h" 10 | #import "WebViewDelegate.h" 11 | 12 | @implementation Viz2ViewController{ 13 | WebViewDelegate *webViewDelegate; 14 | } 15 | 16 | - (void)viewDidLoad { 17 | [super viewDidLoad]; 18 | // Do any additional setup after loading the view, typically from a nib. 19 | 20 | NSURL *url = [NSURL URLWithString:@"https://public.tableau.com/views/10_0ClinicAnalytics/ClinicAnalytics?:embed=y&:tooltip=n&:toolbar=n&:showVizHome=no"]; 21 | 22 | webViewDelegate = [[WebViewDelegate alloc] init]; 23 | self.webView.navigationDelegate = webViewDelegate; 24 | 25 | [self.webView loadRequest:[NSURLRequest requestWithURL: url]]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Viz3ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // Viz3ViewController.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Viz3ViewController : UIViewController 12 | 13 | @property (strong, nonatomic) IBOutlet WKWebView *webView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/Viz3ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // Viz3ViewController.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "Viz3ViewController.h" 10 | #import "WebViewDelegate.h" 11 | 12 | @implementation Viz3ViewController{ 13 | WebViewDelegate *webViewDelegate; 14 | } 15 | 16 | - (void)viewDidLoad { 17 | [super viewDidLoad]; 18 | // Do any additional setup after loading the view, typically from a nib. 19 | 20 | NSURL *url = [NSURL URLWithString:@"https://public.tableau.com/views/10_0SuperstoreSales/Overview?:embed=y&:tooltip=n&:toolbar=n&:showVizHome=no"]; 21 | 22 | webViewDelegate = [[WebViewDelegate alloc] init]; 23 | self.webView.navigationDelegate = webViewDelegate; 24 | 25 | [self.webView loadRequest:[NSURLRequest requestWithURL: url]]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/WebViewDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewDelegate.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface WebViewDelegate : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/WebViewDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewDelegate.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "WebViewDelegate.h" 10 | #import "TokenManager.h" 11 | 12 | @implementation WebViewDelegate 13 | 14 | # pragma mark WKNavigationDelegate 15 | 16 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation; { 17 | if ((webView != nil) && (webView.URL != nil)) { 18 | 19 | // Create tokens if needed; this will check our cookies before requesting. 20 | [TokenManager createTokensIfNeeded:webView.URL]; 21 | } 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/20/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/css/style.css: -------------------------------------------------------------------------------- 1 | /* TS: Add your own css customization here or in the scss folder for Sass customization */ 2 | 3 | /* TS: Styling for the date picker */ 4 | 5 | .ion-datetime-picker-popup > .popup { 6 | height: 450px; 7 | width: 600px !important; 8 | } 9 | 10 | .ion-datetime-picker { 11 | height: 300px; 12 | } 13 | 14 | .ion-datetime-picker > .row { 15 | height: 45px; 16 | font-size:13px; 17 | } 18 | 19 | ion-content.home { 20 | margin-bottom:-5px 21 | } 22 | 23 | /* TS: Styling for tab-home.html */ 24 | 25 | h1, h2 { 26 | font-family:'Benton Sans Thin',Helvetica,sans-serif; 27 | line-height: 1.25; 28 | text-align: center; 29 | font-weight: 100; 30 | } 31 | 32 | h1.padding { 33 | font-size:22pt; 34 | color:#000; 35 | padding-top:30px; 36 | } 37 | 38 | h2 { 39 | color:#333; 40 | font-size:13pt; 41 | padding-left:10px; 42 | padding-right:10px; 43 | padding-bottom:10px; 44 | } 45 | 46 | div.row { 47 | padding-bottom:0px; 48 | } 49 | 50 | div.card { 51 | padding:0px; 52 | } 53 | 54 | a.card { 55 | padding:0px; 56 | display: block; 57 | text-decoration: none; 58 | } 59 | 60 | div.item-divider { 61 | font-size: 11pt; 62 | } 63 | 64 | div.item-body { 65 | border:0px; 66 | font-size:10pt; 67 | } 68 | 69 | /* TS: Styling for viz-container.html */ 70 | 71 | #vizContainer { 72 | width:100%; 73 | height:100%; 74 | } 75 | 76 | /* TS: Styling for sign-in-promt.html */ 77 | 78 | #signInButton { 79 | margin-top:40px; 80 | } 81 | 82 | #signInButton > button { 83 | background-color:rgb(235,143,80); 84 | color:rgb(255,255,255); 85 | font-family: Verdana, Geneva, sans-serif; 86 | line-height:34px; 87 | font-size:16px; 88 | width:80%; 89 | margin:auto; 90 | } 91 | 92 | /* TS: Styling for calendar-filter.html */ 93 | 94 | [ion-datetime-picker] { 95 | text-align: left; 96 | margin-top:10px; 97 | width:100%; 98 | } 99 | 100 | [ion-datetime-picker] > i.ion-calendar { 101 | padding-left: 10px; 102 | float:right; 103 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/AngularLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/AngularLogo.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/Download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/Download.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/ExploreViz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/ExploreViz.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/HeaderLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/HeaderLogo.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/HelpViz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/HelpViz.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/JavaScriptLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/JavaScriptLogo.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/StoriesViz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/StoriesViz.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/YAABA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/YAABA.jpg -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/adduser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/adduser.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/bbq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/bbq.jpg -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/calendar.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/finduser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/finduser.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/history.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/stats.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/img/videos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/img/videos.png -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

23 | Incorporate Tableau Content in Your Mobile App 24 |

25 |

26 | This sample app demonstrates how to embed vizzes into your company's app. 27 |

28 | 66 | 67 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ion-datetime-picker", 3 | "version": "0.3.1", 4 | "authors": [ 5 | "Kate Miháliková " 6 | ], 7 | "description": "Date and/or time picker for awesome ionic framework", 8 | "main": [ 9 | "release/ion-datetime-picker.min.js", 10 | "release/ion-datetime-picker.min.css" 11 | ], 12 | "keywords": [ 13 | "ion-datetime-picker", 14 | "ionic", 15 | "date", 16 | "time", 17 | "picker", 18 | "cordova", 19 | "phonegap" 20 | ], 21 | "license": "MIT", 22 | "homepage": "https://github.com/katemihalikova/ionic-datetime-picker", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules" 26 | ], 27 | "dependencies": { 28 | "ionic": "^1.0.0-beta.9" 29 | }, 30 | "_release": "0.3.1", 31 | "_resolution": { 32 | "type": "version", 33 | "tag": "v0.3.1", 34 | "commit": "974080cf94df981ae4ab65a39fca71b1545e7d98" 35 | }, 36 | "_source": "https://github.com/katemihalikova/ion-datetime-picker.git", 37 | "_target": "^0.3.1", 38 | "_originalSource": "ion-datetime-picker", 39 | "_direct": true 40 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kate Miháliková 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/README.md: -------------------------------------------------------------------------------- 1 | # ion-datetime-picker 2 | ![GitHub version](https://img.shields.io/github/release/katemihalikova/ion-datetime-picker.svg?style=flat-square) 3 | ![Bower version](https://img.shields.io/bower/v/ion-datetime-picker.svg?style=flat-square) 4 | ![Ionic version](https://img.shields.io/badge/ionic-%5E1.0.0--beta.9-yellow.svg?style=flat-square) 5 | ![GitHub issues](https://img.shields.io/github/issues/katemihalikova/ion-datetime-picker.svg?style=flat-square) 6 | ![License](https://img.shields.io/github/license/katemihalikova/ion-datetime-picker.svg?style=flat-square) 7 | 8 | > Date and/or time picker for awesome [Ionic framework](http://ionicframework.com/) v1 9 | 10 | # Introduction 11 | 12 | I made this component because of poor implementation of native datetime picker in Android webview. How funny it was when I discovered that I can only pick a time between 0:00 and 11:59 on my 24-hour clock phone :) 13 | 14 | *Looking for a [picker](http://blog.ionic.io/ionic-2-fixing-date-inputs-for-the-mobile-web/) that works with Ionic framework v2?* 15 | 16 | # Features 17 | 18 | The ion-datetime-picker component has these features: 19 | - Make Date picker, Time picker, Datetime picker 20 | - Choose Sunday or Monday as the first day of the week 21 | - Use 12-hour or 24-hour clock 22 | - Pick time with or without seconds 23 | - Configure popup title and button labels 24 | - Configure i18n to get weekdays and months in your language 25 | - Configure size of a step 26 | 27 | # Demo 28 | 29 | Demo app is available - enter code `8d75a0ec` into [Ionic View](http://view.ionic.io/). 30 | Live demo is available on [Codepen](http://codepen.io/katemihalikova/full/dYvjzP/). 31 | 32 | #Screenshots 33 | 34 | Date picker 35 | Time picker 36 | Datetime picker 37 | 38 | # Installation 39 | 40 | 1. Use bower to install the new module: 41 | 42 | ```bash 43 | bower install ion-datetime-picker --save 44 | ``` 45 | 46 | 2. Import the `ion-datetime-picker` javascript and css file into your HTML file (or use [wiredep](https://github.com/taptapship/wiredep)): 47 | 48 | ```html 49 | 50 | 51 | ``` 52 | 53 | 3. Add `ion-datetime-picker` as a dependency on your Ionic app: 54 | 55 | ```javascript 56 | angular.module("myApp", ["ionic", "ion-datetime-picker"]); 57 | ``` 58 | 59 | # Usage 60 | 61 | Put the `ion-datetime-picker` directive alongside the `ng-model` wherever you want to tap to show the picker: 62 | ```html 63 | 64 | 65 | {{datetimeValue| date: "yyyy-MM-dd H:mm:ss"}} 66 | 67 | 68 | ``` 69 | 70 | ## Configuration attributes 71 | 72 | ### `date` and `time` attributes 73 | 74 | Choose which picker type is used. When neither is set, I assume both and use the datetime picker. 75 | 76 | ### `monday-first` attribute 77 | 78 | Set this if you want to have Monday as the first day of a week. 79 | 80 | ### `seconds` attribute 81 | 82 | By default, in the time picker, I allow to change only hours and minutes. Set this attribute to use also seconds. 83 | 84 | ### `am-pm` attribute 85 | 86 | By default, in the time picker, I use 24-hour clock. Set this attribute to change it to 12-hour clock. 87 | 88 | ### `month-step`, `hour-step`, `minute-step` and `second-step` attributes 89 | 90 | By default, when any caret button is tapped, I add or subtract 1 particular unit. Set these attributes to change it to anything you want. 91 | 92 | ### `title` and `sub-title` attributes 93 | 94 | Configure the title and sub title of the popup with the picker. 95 | 96 | _HINT: Use `data-title` instead of `title` if you are going to use the app in the desktop browser to prevent leaking of the text into a mouseover tooltip._ 97 | 98 | ### `button-ok` and `button-cancel` attributes 99 | 100 | Configure the text of buttons at the bottom of the picker. 101 | 102 | ### `only-valid` attribute 103 | 104 | Disable/Enable calendar days according to type and date range specified. 105 | 106 | ```html 107 | only-valid="{'after': '2016-04-09'}" 108 | only-valid="{'after': 'today', 'inclusive': true}" 109 | only-valid="{'outside': {'initial': '2016-04-09', 'final': '2016-06-15'}, 'inclusive': true}" 110 | ``` 111 | 112 | Types supported: `'after'`, `'before'`, `'between'` and `'outside'`. If you want to include the day specified, set `'inclusive'` property to `true`. 113 | 114 | ## Internationalization factory 115 | 116 | Simple internationalization option. Inject the `$ionicPickerI18n` factory into your code and set the localized strings. 117 | 118 | ### `weekdays` key 119 | 120 | Array of weekdays abbreviations. `0` is Sunday. If `moment` is installed, I try to get localized data from it, otherwise English ones are used as default. 121 | 122 | ### `months` key 123 | 124 | Array of months names. `0` is January. If `moment` is installed, I try to get localized data from it, otherwise English ones are used as default. 125 | 126 | ### `ok` and `cancel` keys 127 | 128 | Default, global labels of the buttons at the bottom of the picker. 129 | 130 | ```js 131 | angular.module("myApp") 132 | .run(function($ionicPickerI18n) { 133 | $ionicPickerI18n.weekdays = ["Нд", "Lu", "Út", "Mi", "To", "금", "Sá"]; 134 | $ionicPickerI18n.months = ["Janvier", "Febrero", "März", "四月", "Maio", "Kesäkuu", "Červenec", "अगस्त", "Вересень", "Październik", "Νοέμβριος", "డిసెంబర్"]; 135 | $ionicPickerI18n.ok = "オーケー"; 136 | $ionicPickerI18n.cancel = "Cancelar"; 137 | }); 138 | ``` 139 | 140 | ## Daylight saving time 141 | 142 | The datetime picker is using `Date` object with your browser's timezone, including any DST. When you change the date, hour, minute, or second, which sets the time to an invalid value because of moving from 2:00 to 3:00 at the beginning of DST, the time is automatically adjusted to a valid value. On the other hand, when the DST ends, I do NOT take the inserted hour into consideration, but this may be fixed in the future. 143 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ion-datetime-picker", 3 | "version": "0.3.1", 4 | "authors": [ 5 | "Kate Miháliková " 6 | ], 7 | "description": "Date and/or time picker for awesome ionic framework", 8 | "main": [ 9 | "release/ion-datetime-picker.min.js", 10 | "release/ion-datetime-picker.min.css" 11 | ], 12 | "keywords": [ 13 | "ion-datetime-picker", 14 | "ionic", 15 | "date", 16 | "time", 17 | "picker", 18 | "cordova", 19 | "phonegap" 20 | ], 21 | "license": "MIT", 22 | "homepage": "https://github.com/katemihalikova/ionic-datetime-picker", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules" 26 | ], 27 | "dependencies": { 28 | "ionic": "^1.0.0-beta.9" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var sass = require("gulp-sass"); 3 | var minifyHtml = require("gulp-minify-html"); 4 | var ngHtml2js = require("gulp-ng-html2js"); 5 | var ngAnnotate = require("gulp-ng-annotate"); 6 | var iife = require("gulp-iife"); 7 | var uglify = require("gulp-uglify"); 8 | var concat = require("gulp-concat"); 9 | 10 | gulp.task("sass", function() { 11 | return gulp.src("src/picker.scss") 12 | .pipe(concat("ion-datetime-picker.min.scss")) 13 | .pipe(sass({outputStyle: "compressed"})) 14 | .pipe(gulp.dest("release")); 15 | }); 16 | 17 | gulp.task("html", function() { 18 | return gulp.src("src/picker-*.html") 19 | .pipe(minifyHtml({ 20 | empty: true, 21 | spare: true, 22 | quotes: true 23 | })) 24 | .pipe(ngHtml2js({ 25 | moduleName: "ion-datetime-picker", 26 | prefix: "lib/ion-datetime-picker/src/", 27 | declareModule: false 28 | })) 29 | .pipe(concat("ion-datetime-picker.min.js")) 30 | .pipe(gulp.dest("release")); 31 | }); 32 | 33 | gulp.task("js", ["html"], function() { 34 | return gulp.src(["src/picker.js", "src/picker-*.js", "release/ion-datetime-picker.min.js"]) 35 | .pipe(ngAnnotate()) 36 | .pipe(concat("ion-datetime-picker.min.js")) 37 | .pipe(iife()) 38 | .pipe(uglify()) 39 | .pipe(gulp.dest("release")); 40 | }); 41 | 42 | gulp.task("build", ["sass", "js"]); 43 | 44 | gulp.task("default", ["build"]); 45 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ion-datetime-picker", 3 | "version": "0.3.1", 4 | "description": "Date and/or time picker for awesome ionic framework", 5 | "main": [ 6 | "release/ion-datetime-picker.js", 7 | "release/ion-datetime-picker.css" 8 | ], 9 | "scripts": {}, 10 | "author": "https://github.com/katemihalikova, kate@katemihalikova.cz", 11 | "license": "MIT", 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "gulp": "^3.9.0", 15 | "gulp-concat": "^2.6.0", 16 | "gulp-iife": "^0.1.0", 17 | "gulp-minify-html": "^1.0.4", 18 | "gulp-ng-annotate": "^1.1.0", 19 | "gulp-ng-html2js": "^0.2.0", 20 | "gulp-sass": "^2.0.4", 21 | "gulp-uglify": "^1.4.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/release/ion-datetime-picker.min.css: -------------------------------------------------------------------------------- 1 | .ion-datetime-picker .calendar{text-align:center;font-size:12px}.ion-datetime-picker .calendar .col{padding:0}.ion-datetime-picker .calendar .day,.ion-datetime-picker .calendar .weekday{padding:5px}.ion-datetime-picker .calendar .day:hover,.ion-datetime-picker .calendar .day.activated,.ion-datetime-picker .calendar .day.today:hover,.ion-datetime-picker .calendar .day.today.activated{background-color:#bdf;color:black;cursor:pointer}.ion-datetime-picker .calendar .day.today{background-color:#e4e4e4}.ion-datetime-picker .calendar .day.selected,.ion-datetime-picker .calendar .day.selected:hover,.ion-datetime-picker .calendar .day.selected.activated{background-color:#387ef5;color:white}.ion-datetime-picker .calendar .day.disabled,.ion-datetime-picker .calendar .day.disabled:hover,.ion-datetime-picker .calendar .day.disabled.activated{background-color:#ccc}.ion-datetime-picker .calendar .weekday{font-weight:bold}.ion-datetime-picker .month-year{padding:0;text-align:center}.ion-datetime-picker .month-year select{width:100%}.ion-datetime-picker .month-year .button{padding:0;width:100%;height:25px;min-width:0;min-height:0}.ion-datetime-picker .month-year .item-input{height:25px;padding:0;margin:0}.ion-datetime-picker .month-year .item-input.item-select:after{right:5px}.ion-datetime-picker .month-year .item-input input,.ion-datetime-picker .month-year .item-input select{font-size:12px;width:100%;height:100%;max-width:none;line-height:20px}.ion-datetime-picker .month-year .item-input select{left:0;padding:0 15px 0 1px;direction:ltr}.ion-datetime-picker .month-year .item-input input{text-align:center;padding:0 5px}.ion-datetime-picker .month-year .item-input input.ng-invalid{background-color:#ffe4ea}.ion-datetime-picker .month-year .item-input input::-webkit-outer-spin-button,.ion-datetime-picker .month-year .item-input input::-webkit-inner-spin-button{display:none}.ion-datetime-picker .time-buttons .col{padding:0}.ion-datetime-picker .time-buttons .button{padding:0;width:100%;height:36px;min-width:0;min-height:0}.ion-datetime-picker .time-buttons .button:before{line-height:35px}.ion-datetime-picker .time-buttons:first-child{padding-top:0}.ion-datetime-picker .time-buttons:last-child{padding-bottom:0}.ion-datetime-picker .time .col{padding:0}.ion-datetime-picker .time .colon{color:#999;font-size:16px;padding:0;text-align:center;line-height:32px}.ion-datetime-picker .time .item-input{height:35px;padding:0;margin:0}.ion-datetime-picker .time .item-input input{font-size:16px;width:100%;height:100%;max-width:none;text-align:center;padding:0 5px}.ion-datetime-picker .time .item-input input.ng-invalid{background-color:#ffe4ea}.ion-datetime-picker .time .item-input input::-webkit-outer-spin-button,.ion-datetime-picker .time .item-input input::-webkit-inner-spin-button{display:none} 2 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/src/picker-i18n.js: -------------------------------------------------------------------------------- 1 | angular.module("ion-datetime-picker") 2 | .factory("$ionicPickerI18n", function($window) { 3 | return { 4 | ok: "OK", 5 | cancel: "Cancel", 6 | weekdays: $window.moment ? $window.moment.weekdaysMin() : ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], 7 | months: $window.moment ? $window.moment.months() : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] 8 | }; 9 | }); -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/src/picker-popup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 11 | 18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 |
{{i18n.weekdays[weekday]}}
26 |
27 |
28 |
29 |
30 |
33 | {{cellDay}} 34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 58 |
:
59 | 66 |
:
67 | 74 |
75 | 82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
-------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ion-datetime-picker/src/picker.scss: -------------------------------------------------------------------------------- 1 | .ion-datetime-picker { 2 | .calendar { 3 | text-align: center; 4 | font-size: 12px; 5 | 6 | .col { 7 | padding: 0; 8 | } 9 | .day, .weekday { 10 | padding: 5px; 11 | } 12 | .day { 13 | &:hover, &.activated, &.today:hover, &.today.activated { 14 | background-color: #bdf; 15 | color: black; 16 | cursor: pointer; 17 | } 18 | &.today { 19 | background-color: #e4e4e4; 20 | } 21 | &.selected, &.selected:hover, &.selected.activated { 22 | background-color: #387ef5; 23 | color: white; 24 | } 25 | &.disabled, &.disabled:hover, &.disabled.activated { 26 | background-color: #ccc; 27 | } 28 | } 29 | .weekday { 30 | font-weight: bold; 31 | } 32 | } 33 | .month-year { 34 | padding: 0; 35 | text-align: center; 36 | 37 | select { 38 | width: 100%; 39 | } 40 | .button { 41 | padding: 0; 42 | width: 100%; 43 | height: 25px; 44 | min-width: 0; 45 | min-height: 0; 46 | } 47 | .item-input { 48 | height: 25px; 49 | padding: 0; 50 | margin: 0; 51 | 52 | &.item-select:after { 53 | right: 5px; 54 | } 55 | input, select { 56 | font-size: 12px; 57 | width: 100%; 58 | height: 100%; 59 | max-width: none; 60 | line-height: 20px; 61 | } 62 | select { 63 | left: 0; 64 | padding: 0 15px 0 1px; 65 | direction: ltr; 66 | } 67 | input { 68 | text-align: center; 69 | padding: 0 5px; 70 | 71 | &.ng-invalid { 72 | background-color: #ffe4ea; 73 | } 74 | &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { 75 | display: none; 76 | } 77 | } 78 | } 79 | } 80 | 81 | .time-buttons { 82 | .col { 83 | padding: 0; 84 | } 85 | .button { 86 | padding: 0; 87 | width: 100%; 88 | height: 36px; 89 | min-width: 0; 90 | min-height: 0; 91 | } 92 | .button:before { 93 | line-height: 35px; 94 | } 95 | &:first-child { 96 | padding-top: 0; 97 | } 98 | &:last-child { 99 | padding-bottom: 0; 100 | } 101 | } 102 | 103 | .time { 104 | .col { 105 | padding: 0; 106 | } 107 | .colon { 108 | color: #999; 109 | font-size: 16px; 110 | padding: 0; 111 | text-align: center; 112 | line-height: 32px; 113 | } 114 | .item-input { 115 | height: 35px; 116 | padding: 0; 117 | margin: 0; 118 | 119 | input { 120 | font-size: 16px; 121 | width: 100%; 122 | height: 100%; 123 | max-width: none; 124 | text-align: center; 125 | padding: 0 5px; 126 | 127 | &.ng-invalid { 128 | background-color: #ffe4ea; 129 | } 130 | &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { 131 | display: none; 132 | } 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/lib/ionic/fonts/ionicons.eot -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/lib/ionic/fonts/ionicons.ttf -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/Mobile App Bootstrap/www/lib/ionic/fonts/ionicons.woff -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/js/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.5.3 3 | (c) 2010-2016 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(Q,d,G){'use strict';function H(t,g){g=g||{};d.forEach(g,function(d,q){delete g[q]});for(var q in t)!t.hasOwnProperty(q)||"$"===q.charAt(0)&&"$"===q.charAt(1)||(g[q]=t[q]);return g}var z=d.$$minErr("$resource"),N=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var t=/^https?:\/\/[^\/]*/,g=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}}; 7 | this.$get=["$http","$log","$q","$timeout",function(q,M,I,J){function A(d,h){return encodeURIComponent(d).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,h?"%20":"+")}function B(d,h){this.template=d;this.defaults=v({},g.defaults,h);this.urlParams={}}function K(e,h,n,k){function c(a,b){var c={};b=v({},h,b);u(b,function(b,h){x(b)&&(b=b());var f;if(b&&b.charAt&&"@"==b.charAt(0)){f=a;var l=b.substr(1);if(null==l||""===l||"hasOwnProperty"===l||!N.test("."+ 8 | l))throw z("badmember",l);for(var l=l.split("."),m=0,k=l.length;m=document.documentMode&&n(g);a=g.innerHTML;g.innerHTML=a}while(a!==g.innerHTML);for(b=g.firstChild;b;){switch(b.nodeType){case 1:c.start(b.nodeName.toLowerCase(),E(b.attributes)); 7 | break;case 3:c.chars(b.textContent)}var d;if(!(d=b.firstChild)&&(1==b.nodeType&&c.end(b.nodeName.toLowerCase()),d=b.nextSibling,!d))for(;null==d;){b=b.parentNode;if(b===g)break;d=b.nextSibling;1==b.nodeType&&c.end(b.nodeName.toLowerCase())}b=d}for(;b=g.firstChild;)g.removeChild(b)}function E(a){for(var c={},b=0,d=a.length;b/g,">")}function v(a,c){var b=!1,d=e.bind(a,a.push);return{start:function(a,f){a=e.lowercase(a);!b&&H[a]&&(b=a);b||!0!==t[a]||(d("<"),d(a),e.forEach(f,function(b,f){var g=e.lowercase(f),h="img"===a&&"src"===g||"background"===g;!0!==I[g]||!0===y[g]&&!c(b,h)||(d(" "),d(f),d('="'),d(x(b)),d('"'))}),d(">"))},end:function(a){a=e.lowercase(a);b||!0!==t[a]||!0===z[a]||(d(""));a== 9 | b&&(b=!1)},chars:function(a){b||d(x(a))}}}function n(a){if(a.nodeType===Node.ELEMENT_NODE)for(var c=a.attributes,b=0,d=c.length;b"\u201d\u2019]/i,b=/^mailto:/i,d=e.$$minErr("linky"),g=e.isString;return function(f,h,m){function k(a){a&&p.push(C(a))}function q(a,b){var c;p.push("');k(b);p.push("")}if(null==f||""===f)return f;if(!g(f))throw d("notstring",f);for(var r=f,p=[],s,n;f=r.match(c);)s=f[0],f[2]||f[4]||(s=(f[3]?"http://":"mailto:")+s),n=f.index,k(r.substr(0,n)),q(s,f[0].replace(b,"")),r=r.substring(n+f[0].length);k(r);return a(p.join(""))}}])})(window,window.angular); 15 | //# sourceMappingURL=angular-sanitize.min.js.map 16 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_action-sheet.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Action Sheets 3 | * -------------------------------------------------- 4 | */ 5 | 6 | .action-sheet-backdrop { 7 | @include transition(background-color 150ms ease-in-out); 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | z-index: $z-index-action-sheet; 12 | width: 100%; 13 | height: 100%; 14 | background-color: rgba(0,0,0,0); 15 | 16 | &.active { 17 | background-color: rgba(0,0,0,0.4); 18 | } 19 | } 20 | 21 | .action-sheet-wrapper { 22 | @include translate3d(0, 100%, 0); 23 | @include transition(all cubic-bezier(.36, .66, .04, 1) 500ms); 24 | position: absolute; 25 | bottom: 0; 26 | left: 0; 27 | right: 0; 28 | width: 100%; 29 | max-width: 500px; 30 | margin: auto; 31 | } 32 | 33 | .action-sheet-up { 34 | @include translate3d(0, 0, 0); 35 | } 36 | 37 | .action-sheet { 38 | margin-left: $sheet-margin; 39 | margin-right: $sheet-margin; 40 | width: auto; 41 | z-index: $z-index-action-sheet; 42 | overflow: hidden; 43 | 44 | .button { 45 | display: block; 46 | padding: 1px; 47 | width: 100%; 48 | border-radius: 0; 49 | border-color: $sheet-options-border-color; 50 | background-color: transparent; 51 | 52 | color: $sheet-options-text-color; 53 | font-size: 21px; 54 | 55 | &:hover { 56 | color: $sheet-options-text-color; 57 | } 58 | &.destructive { 59 | color: #ff3b30; 60 | &:hover { 61 | color: #ff3b30; 62 | } 63 | } 64 | } 65 | 66 | .button.active, .button.activated { 67 | box-shadow: none; 68 | border-color: $sheet-options-border-color; 69 | color: $sheet-options-text-color; 70 | background: $sheet-options-bg-active-color; 71 | } 72 | } 73 | 74 | .action-sheet-has-icons .icon { 75 | position: absolute; 76 | left: 16px; 77 | } 78 | 79 | .action-sheet-title { 80 | padding: $sheet-margin * 2; 81 | color: #8f8f8f; 82 | text-align: center; 83 | font-size: 13px; 84 | } 85 | 86 | .action-sheet-group { 87 | margin-bottom: $sheet-margin; 88 | border-radius: $sheet-border-radius; 89 | background-color: #fff; 90 | overflow: hidden; 91 | 92 | .button { 93 | border-width: 1px 0px 0px 0px; 94 | } 95 | .button:first-child:last-child { 96 | border-width: 0; 97 | } 98 | } 99 | 100 | .action-sheet-options { 101 | background: $sheet-options-bg-color; 102 | } 103 | 104 | .action-sheet-cancel { 105 | .button { 106 | font-weight: 500; 107 | } 108 | } 109 | 110 | .action-sheet-open { 111 | pointer-events: none; 112 | 113 | &.modal-open .modal { 114 | pointer-events: none; 115 | } 116 | 117 | .action-sheet-backdrop { 118 | pointer-events: auto; 119 | } 120 | } 121 | 122 | 123 | .platform-android { 124 | 125 | .action-sheet-backdrop.active { 126 | background-color: rgba(0,0,0,0.2); 127 | } 128 | 129 | .action-sheet { 130 | margin: 0; 131 | 132 | .action-sheet-title, 133 | .button { 134 | text-align: left; 135 | border-color: transparent; 136 | font-size: 16px; 137 | color: inherit; 138 | } 139 | 140 | .action-sheet-title { 141 | font-size: 14px; 142 | padding: 16px; 143 | color: #666; 144 | } 145 | 146 | .button.active, 147 | .button.activated { 148 | background: #e8e8e8; 149 | } 150 | } 151 | 152 | .action-sheet-group { 153 | margin: 0; 154 | border-radius: 0; 155 | background-color: #fafafa; 156 | } 157 | 158 | .action-sheet-cancel { 159 | display: none; 160 | } 161 | 162 | .action-sheet-has-icons { 163 | 164 | .button { 165 | padding-left: 56px; 166 | } 167 | 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | 2 | // Slide up from the bottom, used for modals 3 | // ------------------------------- 4 | 5 | .slide-in-up { 6 | @include translate3d(0, 100%, 0); 7 | } 8 | .slide-in-up.ng-enter, 9 | .slide-in-up > .ng-enter { 10 | @include transition(all cubic-bezier(.1, .7, .1, 1) 400ms); 11 | } 12 | .slide-in-up.ng-enter-active, 13 | .slide-in-up > .ng-enter-active { 14 | @include translate3d(0, 0, 0); 15 | } 16 | 17 | .slide-in-up.ng-leave, 18 | .slide-in-up > .ng-leave { 19 | @include transition(all ease-in-out 250ms); 20 | } 21 | 22 | 23 | // Scale Out 24 | // Scale from hero (1 in this case) to zero 25 | // ------------------------------- 26 | 27 | @-webkit-keyframes scaleOut { 28 | from { -webkit-transform: scale(1); opacity: 1; } 29 | to { -webkit-transform: scale(0.8); opacity: 0; } 30 | } 31 | @keyframes scaleOut { 32 | from { transform: scale(1); opacity: 1; } 33 | to { transform: scale(0.8); opacity: 0; } 34 | } 35 | 36 | 37 | // Super Scale In 38 | // Scale from super (1.x) to duper (1 in this case) 39 | // ------------------------------- 40 | 41 | @-webkit-keyframes superScaleIn { 42 | from { -webkit-transform: scale(1.2); opacity: 0; } 43 | to { -webkit-transform: scale(1); opacity: 1 } 44 | } 45 | @keyframes superScaleIn { 46 | from { transform: scale(1.2); opacity: 0; } 47 | to { transform: scale(1); opacity: 1; } 48 | } 49 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_backdrop.scss: -------------------------------------------------------------------------------- 1 | 2 | .backdrop { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | z-index: $z-index-backdrop; 7 | 8 | width: 100%; 9 | height: 100%; 10 | 11 | background-color: $loading-backdrop-bg-color; 12 | 13 | visibility: hidden; 14 | opacity: 0; 15 | 16 | &.visible { 17 | visibility: visible; 18 | } 19 | &.active { 20 | opacity: 1; 21 | } 22 | 23 | @include transition($loading-backdrop-fadein-duration opacity linear); 24 | } 25 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_badge.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Badges 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .badge { 8 | @include badge-style($badge-default-bg, $badge-default-text); 9 | z-index: $z-index-badge; 10 | display: inline-block; 11 | padding: 3px 8px; 12 | min-width: 10px; 13 | border-radius: $badge-border-radius; 14 | vertical-align: baseline; 15 | text-align: center; 16 | white-space: nowrap; 17 | font-weight: $badge-font-weight; 18 | font-size: $badge-font-size; 19 | line-height: $badge-line-height; 20 | 21 | &:empty { 22 | display: none; 23 | } 24 | } 25 | 26 | //Be sure to override specificity of rule that 'badge color matches tab color by default' 27 | .tabs .tab-item .badge, 28 | .badge { 29 | &.badge-light { 30 | @include badge-style($badge-light-bg, $badge-light-text); 31 | } 32 | &.badge-stable { 33 | @include badge-style($badge-stable-bg, $badge-stable-text); 34 | } 35 | &.badge-positive { 36 | @include badge-style($badge-positive-bg, $badge-positive-text); 37 | } 38 | &.badge-calm { 39 | @include badge-style($badge-calm-bg, $badge-calm-text); 40 | } 41 | &.badge-assertive { 42 | @include badge-style($badge-assertive-bg, $badge-assertive-text); 43 | } 44 | &.badge-balanced { 45 | @include badge-style($badge-balanced-bg, $badge-balanced-text); 46 | } 47 | &.badge-energized { 48 | @include badge-style($badge-energized-bg, $badge-energized-text); 49 | } 50 | &.badge-royal { 51 | @include badge-style($badge-royal-bg, $badge-royal-text); 52 | } 53 | &.badge-dark { 54 | @include badge-style($badge-dark-bg, $badge-dark-text); 55 | } 56 | } 57 | 58 | // Quick fix for labels/badges in buttons 59 | .button .badge { 60 | position: relative; 61 | top: -1px; 62 | } 63 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_button-bar.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Button Bar 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .button-bar { 8 | @include display-flex(); 9 | @include flex(1); 10 | width: 100%; 11 | 12 | &.button-bar-inline { 13 | display: block; 14 | width: auto; 15 | 16 | @include clearfix(); 17 | 18 | > .button { 19 | width: auto; 20 | display: inline-block; 21 | float: left; 22 | } 23 | } 24 | 25 | &.bar-light > .button { 26 | border-color: $button-light-border; 27 | } 28 | &.bar-stable > .button { 29 | border-color: $button-stable-border; 30 | } 31 | &.bar-positive > .button { 32 | border-color: $button-positive-border; 33 | } 34 | &.bar-calm > .button { 35 | border-color: $button-calm-border; 36 | } 37 | &.bar-assertive > .button { 38 | border-color: $button-assertive-border; 39 | } 40 | &.bar-balanced > .button { 41 | border-color: $button-balanced-border; 42 | } 43 | &.bar-energized > .button { 44 | border-color: $button-energized-border; 45 | } 46 | &.bar-royal > .button { 47 | border-color: $button-royal-border; 48 | } 49 | &.bar-dark > .button { 50 | border-color: $button-dark-border; 51 | } 52 | } 53 | 54 | .button-bar > .button { 55 | @include flex(1); 56 | display: block; 57 | 58 | overflow: hidden; 59 | 60 | padding: 0 16px; 61 | 62 | width: 0; 63 | 64 | border-width: 1px 0px 1px 1px; 65 | border-radius: 0; 66 | text-align: center; 67 | text-overflow: ellipsis; 68 | white-space: nowrap; 69 | 70 | &:before, 71 | .icon:before { 72 | line-height: 44px; 73 | } 74 | 75 | &:first-child { 76 | border-radius: $button-border-radius 0px 0px $button-border-radius; 77 | } 78 | &:last-child { 79 | border-right-width: 1px; 80 | border-radius: 0px $button-border-radius $button-border-radius 0px; 81 | } 82 | &:only-child { 83 | border-radius: $button-border-radius; 84 | } 85 | } 86 | 87 | .button-bar > .button-small { 88 | &:before, 89 | .icon:before { 90 | line-height: 28px; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_checkbox.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Checkbox 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .checkbox { 8 | // set the color defaults 9 | @include checkbox-style($checkbox-off-border-default, $checkbox-on-bg-default, $checkbox-on-border-default); 10 | 11 | position: relative; 12 | display: inline-block; 13 | padding: ($checkbox-height / 4) ($checkbox-width / 4); 14 | cursor: pointer; 15 | } 16 | .checkbox-light { 17 | @include checkbox-style($checkbox-off-border-light, $checkbox-on-bg-light, $checkbox-off-border-light); 18 | } 19 | .checkbox-stable { 20 | @include checkbox-style($checkbox-off-border-stable, $checkbox-on-bg-stable, $checkbox-off-border-stable); 21 | } 22 | .checkbox-positive { 23 | @include checkbox-style($checkbox-off-border-positive, $checkbox-on-bg-positive, $checkbox-off-border-positive); 24 | } 25 | .checkbox-calm { 26 | @include checkbox-style($checkbox-off-border-calm, $checkbox-on-bg-calm, $checkbox-off-border-calm); 27 | } 28 | .checkbox-assertive { 29 | @include checkbox-style($checkbox-off-border-assertive, $checkbox-on-bg-assertive, $checkbox-off-border-assertive); 30 | } 31 | .checkbox-balanced { 32 | @include checkbox-style($checkbox-off-border-balanced, $checkbox-on-bg-balanced, $checkbox-off-border-balanced); 33 | } 34 | .checkbox-energized{ 35 | @include checkbox-style($checkbox-off-border-energized, $checkbox-on-bg-energized, $checkbox-off-border-energized); 36 | } 37 | .checkbox-royal { 38 | @include checkbox-style($checkbox-off-border-royal, $checkbox-on-bg-royal, $checkbox-off-border-royal); 39 | } 40 | .checkbox-dark { 41 | @include checkbox-style($checkbox-off-border-dark, $checkbox-on-bg-dark, $checkbox-off-border-dark); 42 | } 43 | 44 | .checkbox input:disabled:before, 45 | .checkbox input:disabled + .checkbox-icon:before { 46 | border-color: $checkbox-off-border-light; 47 | } 48 | 49 | .checkbox input:disabled:checked:before, 50 | .checkbox input:disabled:checked + .checkbox-icon:before { 51 | background: $checkbox-on-bg-light; 52 | } 53 | 54 | 55 | .checkbox.checkbox-input-hidden input { 56 | display: none !important; 57 | } 58 | 59 | .checkbox input, 60 | .checkbox-icon { 61 | position: relative; 62 | width: $checkbox-width; 63 | height: $checkbox-height; 64 | display: block; 65 | border: 0; 66 | background: transparent; 67 | cursor: pointer; 68 | -webkit-appearance: none; 69 | 70 | &:before { 71 | // what the checkbox looks like when its not checked 72 | display: table; 73 | width: 100%; 74 | height: 100%; 75 | border-width: $checkbox-border-width; 76 | border-style: solid; 77 | border-radius: $checkbox-border-radius; 78 | background: $checkbox-off-bg-color; 79 | content: ' '; 80 | @include transition(background-color 20ms ease-in-out); 81 | } 82 | } 83 | 84 | .checkbox input:checked:before, 85 | input:checked + .checkbox-icon:before { 86 | border-width: $checkbox-border-width + 1; 87 | } 88 | 89 | // the checkmark within the box 90 | .checkbox input:after, 91 | .checkbox-icon:after { 92 | @include transition(opacity .05s ease-in-out); 93 | @include rotate(-45deg); 94 | position: absolute; 95 | top: 33%; 96 | left: 25%; 97 | display: table; 98 | width: ($checkbox-width / 2); 99 | height: ($checkbox-width / 4) - 1; 100 | border: $checkbox-check-width solid $checkbox-check-color; 101 | border-top: 0; 102 | border-right: 0; 103 | content: ' '; 104 | opacity: 0; 105 | } 106 | 107 | .platform-android .checkbox-platform input:before, 108 | .platform-android .checkbox-platform .checkbox-icon:before, 109 | .checkbox-square input:before, 110 | .checkbox-square .checkbox-icon:before { 111 | border-radius: 2px; 112 | width: 72%; 113 | height: 72%; 114 | margin-top: 14%; 115 | margin-left: 14%; 116 | border-width: 2px; 117 | } 118 | 119 | .platform-android .checkbox-platform input:after, 120 | .platform-android .checkbox-platform .checkbox-icon:after, 121 | .checkbox-square input:after, 122 | .checkbox-square .checkbox-icon:after { 123 | border-width: 2px; 124 | top: 19%; 125 | left: 25%; 126 | width: ($checkbox-width / 2) - 1; 127 | height: 7px; 128 | } 129 | 130 | .platform-android .item-checkbox-right .checkbox-square .checkbox-icon::after { 131 | top: 31%; 132 | } 133 | 134 | .grade-c .checkbox input:after, 135 | .grade-c .checkbox-icon:after { 136 | @include rotate(0); 137 | top: 3px; 138 | left: 4px; 139 | border: none; 140 | color: $checkbox-check-color; 141 | content: '\2713'; 142 | font-weight: bold; 143 | font-size: 20px; 144 | } 145 | 146 | // what the checkmark looks like when its checked 147 | .checkbox input:checked:after, 148 | input:checked + .checkbox-icon:after { 149 | opacity: 1; 150 | } 151 | 152 | // make sure item content have enough padding on left to fit the checkbox 153 | .item-checkbox { 154 | padding-left: ($item-padding * 2) + $checkbox-width; 155 | 156 | &.active { 157 | box-shadow: none; 158 | } 159 | } 160 | 161 | // position the checkbox to the left within an item 162 | .item-checkbox .checkbox { 163 | position: absolute; 164 | top: 50%; 165 | right: $item-padding / 2; 166 | left: $item-padding / 2; 167 | z-index: $z-index-item-checkbox; 168 | margin-top: (($checkbox-height + ($checkbox-height / 2)) / 2) * -1; 169 | } 170 | 171 | 172 | .item-checkbox.item-checkbox-right { 173 | padding-right: ($item-padding * 2) + $checkbox-width; 174 | padding-left: $item-padding; 175 | } 176 | 177 | .item-checkbox-right .checkbox input, 178 | .item-checkbox-right .checkbox-icon { 179 | float: right; 180 | } 181 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_grid.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid 3 | * -------------------------------------------------- 4 | * Using flexbox for the grid, inspired by Philip Walton: 5 | * http://philipwalton.github.io/solved-by-flexbox/demos/grids/ 6 | * By default each .col within a .row will evenly take up 7 | * available width, and the height of each .col with take 8 | * up the height of the tallest .col in the same .row. 9 | */ 10 | 11 | .row { 12 | @include display-flex(); 13 | padding: ($grid-padding-width / 2); 14 | width: 100%; 15 | } 16 | 17 | .row-wrap { 18 | @include flex-wrap(wrap); 19 | } 20 | 21 | .row-no-padding { 22 | padding: 0; 23 | 24 | > .col { 25 | padding: 0; 26 | } 27 | } 28 | 29 | .row + .row { 30 | margin-top: ($grid-padding-width / 2) * -1; 31 | padding-top: 0; 32 | } 33 | 34 | .col { 35 | @include flex(1); 36 | display: block; 37 | padding: ($grid-padding-width / 2); 38 | width: 100%; 39 | } 40 | 41 | 42 | /* Vertically Align Columns */ 43 | /* .row-* vertically aligns every .col in the .row */ 44 | .row-top { 45 | @include align-items(flex-start); 46 | } 47 | .row-bottom { 48 | @include align-items(flex-end); 49 | } 50 | .row-center { 51 | @include align-items(center); 52 | } 53 | .row-stretch { 54 | @include align-items(stretch); 55 | } 56 | .row-baseline { 57 | @include align-items(baseline); 58 | } 59 | 60 | /* .col-* vertically aligns an individual .col */ 61 | .col-top { 62 | @include align-self(flex-start); 63 | } 64 | .col-bottom { 65 | @include align-self(flex-end); 66 | } 67 | .col-center { 68 | @include align-self(center); 69 | } 70 | 71 | /* Column Offsets */ 72 | .col-offset-10 { 73 | margin-left: 10%; 74 | } 75 | .col-offset-20 { 76 | margin-left: 20%; 77 | } 78 | .col-offset-25 { 79 | margin-left: 25%; 80 | } 81 | .col-offset-33, .col-offset-34 { 82 | margin-left: 33.3333%; 83 | } 84 | .col-offset-50 { 85 | margin-left: 50%; 86 | } 87 | .col-offset-66, .col-offset-67 { 88 | margin-left: 66.6666%; 89 | } 90 | .col-offset-75 { 91 | margin-left: 75%; 92 | } 93 | .col-offset-80 { 94 | margin-left: 80%; 95 | } 96 | .col-offset-90 { 97 | margin-left: 90%; 98 | } 99 | 100 | 101 | /* Explicit Column Percent Sizes */ 102 | /* By default each grid column will evenly distribute */ 103 | /* across the grid. However, you can specify individual */ 104 | /* columns to take up a certain size of the available area */ 105 | .col-10 { 106 | @include flex(0, 0, 10%); 107 | max-width: 10%; 108 | } 109 | .col-20 { 110 | @include flex(0, 0, 20%); 111 | max-width: 20%; 112 | } 113 | .col-25 { 114 | @include flex(0, 0, 25%); 115 | max-width: 25%; 116 | } 117 | .col-33, .col-34 { 118 | @include flex(0, 0, 33.3333%); 119 | max-width: 33.3333%; 120 | } 121 | .col-40 { 122 | @include flex(0, 0, 40%); 123 | max-width: 40%; 124 | } 125 | .col-50 { 126 | @include flex(0, 0, 50%); 127 | max-width: 50%; 128 | } 129 | .col-60 { 130 | @include flex(0, 0, 60%); 131 | max-width: 60%; 132 | } 133 | .col-66, .col-67 { 134 | @include flex(0, 0, 66.6666%); 135 | max-width: 66.6666%; 136 | } 137 | .col-75 { 138 | @include flex(0, 0, 75%); 139 | max-width: 75%; 140 | } 141 | .col-80 { 142 | @include flex(0, 0, 80%); 143 | max-width: 80%; 144 | } 145 | .col-90 { 146 | @include flex(0, 0, 90%); 147 | max-width: 90%; 148 | } 149 | 150 | 151 | /* Responsive Grid Classes */ 152 | /* Adding a class of responsive-X to a row */ 153 | /* will trigger the flex-direction to */ 154 | /* change to column and add some margin */ 155 | /* to any columns in the row for clearity */ 156 | 157 | @include responsive-grid-break('.responsive-sm', $grid-responsive-sm-break); 158 | @include responsive-grid-break('.responsive-md', $grid-responsive-md-break); 159 | @include responsive-grid-break('.responsive-lg', $grid-responsive-lg-break); 160 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_list.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Lists 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .list { 8 | position: relative; 9 | padding-top: $item-border-width; 10 | padding-bottom: $item-border-width; 11 | padding-left: 0; // reset padding because ul and ol 12 | margin-bottom: 20px; 13 | } 14 | .list:last-child { 15 | margin-bottom: 0px; 16 | &.card{ 17 | margin-bottom:40px; 18 | } 19 | } 20 | 21 | 22 | /** 23 | * List Header 24 | * -------------------------------------------------- 25 | */ 26 | 27 | .list-header { 28 | margin-top: $list-header-margin-top; 29 | padding: $list-header-padding; 30 | background-color: $list-header-bg; 31 | color: $list-header-color; 32 | font-weight: bold; 33 | } 34 | 35 | // when its a card make sure it doesn't duplicate top and bottom borders 36 | .card.list .list-item { 37 | padding-right: 1px; 38 | padding-left: 1px; 39 | } 40 | 41 | 42 | /** 43 | * Cards and Inset Lists 44 | * -------------------------------------------------- 45 | * A card and list-inset are close to the same thing, except a card as a box shadow. 46 | */ 47 | 48 | .card, 49 | .list-inset { 50 | overflow: hidden; 51 | margin: ($content-padding * 2) $content-padding; 52 | border-radius: $card-border-radius; 53 | background-color: $card-body-bg; 54 | } 55 | 56 | .card { 57 | padding-top: $item-border-width; 58 | padding-bottom: $item-border-width; 59 | box-shadow: $card-box-shadow; 60 | 61 | .item { 62 | border-left: 0; 63 | border-right: 0; 64 | } 65 | .item:first-child { 66 | border-top: 0; 67 | } 68 | .item:last-child { 69 | border-bottom: 0; 70 | } 71 | } 72 | 73 | .padding { 74 | .card, .list-inset { 75 | margin-left: 0; 76 | margin-right: 0; 77 | } 78 | } 79 | 80 | .card .item, 81 | .list-inset .item, 82 | .padding > .list .item 83 | { 84 | &:first-child { 85 | border-top-left-radius: $card-border-radius; 86 | border-top-right-radius: $card-border-radius; 87 | 88 | .item-content { 89 | border-top-left-radius: $card-border-radius; 90 | border-top-right-radius: $card-border-radius; 91 | } 92 | } 93 | &:last-child { 94 | border-bottom-right-radius: $card-border-radius; 95 | border-bottom-left-radius: $card-border-radius; 96 | 97 | .item-content { 98 | border-bottom-right-radius: $card-border-radius; 99 | border-bottom-left-radius: $card-border-radius; 100 | } 101 | } 102 | } 103 | 104 | .card .item:last-child, 105 | .list-inset .item:last-child { 106 | margin-bottom: $item-border-width * -1; 107 | } 108 | 109 | .card .item, 110 | .list-inset .item, 111 | .padding > .list .item, 112 | .padding-horizontal > .list .item { 113 | margin-right: 0; 114 | margin-left: 0; 115 | 116 | &.item-input input { 117 | padding-right: 44px; 118 | } 119 | } 120 | .padding-left > .list .item { 121 | margin-left: 0; 122 | } 123 | .padding-right > .list .item { 124 | margin-right: 0; 125 | } 126 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_loading.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Loading 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .loading-container { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | right: 0; 12 | bottom: 0; 13 | 14 | z-index: $z-index-loading; 15 | 16 | @include display-flex(); 17 | @include justify-content(center); 18 | @include align-items(center); 19 | 20 | @include transition(0.2s opacity linear); 21 | visibility: hidden; 22 | opacity: 0; 23 | 24 | &:not(.visible) .icon, 25 | &:not(.visible) .spinner{ 26 | display: none; 27 | } 28 | &.visible { 29 | visibility: visible; 30 | } 31 | &.active { 32 | opacity: 1; 33 | } 34 | 35 | .loading { 36 | padding: $loading-padding; 37 | 38 | border-radius: $loading-border-radius; 39 | background-color: $loading-bg-color; 40 | 41 | color: $loading-text-color; 42 | 43 | text-align: center; 44 | text-overflow: ellipsis; 45 | font-size: $loading-font-size; 46 | 47 | h1, h2, h3, h4, h5, h6 { 48 | color: $loading-text-color; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_menu.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Menus 4 | * -------------------------------------------------- 5 | * Side panel structure 6 | */ 7 | 8 | .menu { 9 | position: absolute; 10 | top: 0; 11 | bottom: 0; 12 | z-index: $z-index-menu; 13 | overflow: hidden; 14 | 15 | min-height: 100%; 16 | max-height: 100%; 17 | width: $menu-width; 18 | 19 | background-color: $menu-bg; 20 | 21 | .scroll-content { 22 | z-index: $z-index-menu-scroll-content; 23 | } 24 | 25 | .bar-header { 26 | z-index: $z-index-menu-bar-header; 27 | } 28 | } 29 | 30 | .menu-content { 31 | @include transform(none); 32 | box-shadow: $menu-side-shadow; 33 | } 34 | 35 | .menu-open .menu-content .pane, 36 | .menu-open .menu-content .scroll-content { 37 | pointer-events: none; 38 | } 39 | .menu-open .menu-content .scroll-content .scroll { 40 | pointer-events: none; 41 | } 42 | .menu-open .menu-content .scroll-content:not(.overflow-scroll) { 43 | overflow: hidden; 44 | } 45 | 46 | .grade-b .menu-content, 47 | .grade-c .menu-content { 48 | @include box-sizing(content-box); 49 | right: -1px; 50 | left: -1px; 51 | border-right: 1px solid #ccc; 52 | border-left: 1px solid #ccc; 53 | box-shadow: none; 54 | } 55 | 56 | .menu-left { 57 | left: 0; 58 | } 59 | 60 | .menu-right { 61 | right: 0; 62 | } 63 | 64 | .aside-open.aside-resizing .menu-right { 65 | display: none; 66 | } 67 | 68 | .menu-animated { 69 | @include transition-transform($menu-animation-speed ease); 70 | } 71 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_modal.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Modals 4 | * -------------------------------------------------- 5 | * Modals are independent windows that slide in from off-screen. 6 | */ 7 | 8 | .modal-backdrop, 9 | .modal-backdrop-bg { 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | z-index: $z-index-modal; 14 | width: 100%; 15 | height: 100%; 16 | } 17 | 18 | .modal-backdrop-bg { 19 | pointer-events: none; 20 | } 21 | 22 | .modal { 23 | display: block; 24 | position: absolute; 25 | top: 0; 26 | z-index: $z-index-modal; 27 | overflow: hidden; 28 | min-height: 100%; 29 | width: 100%; 30 | background-color: $modal-bg-color; 31 | } 32 | 33 | @media (min-width: $modal-inset-mode-break-point) { 34 | // inset mode is when the modal doesn't fill the entire 35 | // display but instead is centered within a large display 36 | .modal { 37 | top: $modal-inset-mode-top; 38 | right: $modal-inset-mode-right; 39 | bottom: $modal-inset-mode-bottom; 40 | left: $modal-inset-mode-left; 41 | min-height: $modal-inset-mode-min-height; 42 | width: (100% - $modal-inset-mode-left - $modal-inset-mode-right); 43 | } 44 | 45 | .modal.ng-leave-active { 46 | bottom: 0; 47 | } 48 | 49 | // remove ios header padding from inset header 50 | .platform-ios.platform-cordova .modal-wrapper .modal { 51 | .bar-header:not(.bar-subheader) { 52 | height: $bar-height; 53 | > * { 54 | margin-top: 0; 55 | } 56 | } 57 | .tabs-top > .tabs, 58 | .tabs.tabs-top { 59 | top: $bar-height; 60 | } 61 | .has-header, 62 | .bar-subheader { 63 | top: $bar-height; 64 | } 65 | .has-subheader { 66 | top: $bar-height + $bar-subheader-height; 67 | } 68 | .has-header.has-tabs-top { 69 | top: $bar-height + $tabs-height; 70 | } 71 | .has-header.has-subheader.has-tabs-top { 72 | top: $bar-height + $bar-subheader-height + $tabs-height; 73 | } 74 | } 75 | 76 | .modal-backdrop-bg { 77 | @include transition(opacity 300ms ease-in-out); 78 | background-color: $modal-backdrop-bg-active; 79 | opacity: 0; 80 | } 81 | 82 | .active .modal-backdrop-bg { 83 | opacity: 0.5; 84 | } 85 | } 86 | 87 | // disable clicks on all but the modal 88 | .modal-open { 89 | pointer-events: none; 90 | 91 | .modal, 92 | .modal-backdrop { 93 | pointer-events: auto; 94 | } 95 | // prevent clicks on modal when loading overlay is active though 96 | &.loading-active { 97 | .modal, 98 | .modal-backdrop { 99 | pointer-events: none; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_platform.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Platform 4 | * -------------------------------------------------- 5 | * Platform specific tweaks 6 | */ 7 | 8 | .platform-ios.platform-cordova { 9 | // iOS has a status bar which sits on top of the header. 10 | // Bump down everything to make room for it. However, if 11 | // if its in Cordova, and set to fullscreen, then disregard the bump. 12 | &:not(.fullscreen) { 13 | .bar-header:not(.bar-subheader) { 14 | height: $bar-height + $ios-statusbar-height; 15 | 16 | &.item-input-inset .item-input-wrapper { 17 | margin-top: 19px !important; 18 | } 19 | 20 | > * { 21 | margin-top: $ios-statusbar-height; 22 | } 23 | } 24 | .tabs-top > .tabs, 25 | .tabs.tabs-top { 26 | top: $bar-height + $ios-statusbar-height; 27 | } 28 | 29 | .has-header, 30 | .bar-subheader { 31 | top: $bar-height + $ios-statusbar-height; 32 | } 33 | .has-subheader { 34 | top: $bar-height + $bar-subheader-height + $ios-statusbar-height; 35 | } 36 | .has-header.has-tabs-top { 37 | top: $bar-height + $tabs-height + $ios-statusbar-height; 38 | } 39 | .has-header.has-subheader.has-tabs-top { 40 | top: $bar-height + $bar-subheader-height + $tabs-height + $ios-statusbar-height; 41 | } 42 | } 43 | .popover{ 44 | .bar-header:not(.bar-subheader) { 45 | height: $bar-height; 46 | &.item-input-inset .item-input-wrapper { 47 | margin-top: -1px; 48 | } 49 | > * { 50 | margin-top: 0; 51 | } 52 | } 53 | .has-header, 54 | .bar-subheader { 55 | top: $bar-height; 56 | } 57 | .has-subheader { 58 | top: $bar-height + $bar-subheader-height; 59 | } 60 | } 61 | &.status-bar-hide { 62 | // Cordova doesn't adjust the body height correctly, this makes up for it 63 | margin-bottom: 20px; 64 | } 65 | } 66 | 67 | @media (orientation:landscape) { 68 | .platform-ios.platform-browser.platform-ipad { 69 | position: fixed; // required for iPad 7 Safari 70 | } 71 | } 72 | 73 | .platform-c:not(.enable-transitions) * { 74 | // disable transitions on grade-c devices (Android 2) 75 | -webkit-transition: none !important; 76 | transition: none !important; 77 | } 78 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_popover.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Popovers 4 | * -------------------------------------------------- 5 | * Popovers are independent views which float over content 6 | */ 7 | 8 | .popover-backdrop { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | z-index: $z-index-popover; 13 | width: 100%; 14 | height: 100%; 15 | background-color: $popover-backdrop-bg-inactive; 16 | 17 | &.active { 18 | background-color: $popover-backdrop-bg-active; 19 | } 20 | } 21 | 22 | .popover { 23 | position: absolute; 24 | top: 25%; 25 | left: 50%; 26 | z-index: $z-index-popover; 27 | display: block; 28 | margin-top: 12px; 29 | margin-left: -$popover-width / 2; 30 | height: $popover-height; 31 | width: $popover-width; 32 | background-color: $popover-bg-color; 33 | box-shadow: $popover-box-shadow; 34 | opacity: 0; 35 | 36 | .item:first-child { 37 | border-top: 0; 38 | } 39 | 40 | .item:last-child { 41 | border-bottom: 0; 42 | } 43 | 44 | &.popover-bottom { 45 | margin-top: -12px; 46 | } 47 | } 48 | 49 | 50 | // Set popover border-radius 51 | .popover, 52 | .popover .bar-header { 53 | border-radius: $popover-border-radius; 54 | } 55 | .popover .scroll-content { 56 | z-index: 1; 57 | margin: 2px 0; 58 | } 59 | .popover .bar-header { 60 | border-bottom-right-radius: 0; 61 | border-bottom-left-radius: 0; 62 | } 63 | .popover .has-header { 64 | border-top-right-radius: 0; 65 | border-top-left-radius: 0; 66 | } 67 | .popover-arrow { 68 | display: none; 69 | } 70 | 71 | 72 | // iOS Popover 73 | .platform-ios { 74 | 75 | .popover { 76 | box-shadow: $popover-box-shadow-ios; 77 | border-radius: $popover-border-radius-ios; 78 | } 79 | .popover .bar-header { 80 | @include border-top-radius($popover-border-radius-ios); 81 | } 82 | .popover .scroll-content { 83 | margin: 8px 0; 84 | border-radius: $popover-border-radius-ios; 85 | } 86 | .popover .scroll-content.has-header { 87 | margin-top: 0; 88 | } 89 | .popover-arrow { 90 | position: absolute; 91 | display: block; 92 | top: -17px; 93 | width: 30px; 94 | height: 19px; 95 | overflow: hidden; 96 | 97 | &:after { 98 | position: absolute; 99 | top: 12px; 100 | left: 5px; 101 | width: 20px; 102 | height: 20px; 103 | background-color: $popover-bg-color; 104 | border-radius: 3px; 105 | content: ''; 106 | @include rotate(-45deg); 107 | } 108 | } 109 | .popover-bottom .popover-arrow { 110 | top: auto; 111 | bottom: -10px; 112 | &:after { 113 | top: -6px; 114 | } 115 | } 116 | } 117 | 118 | 119 | // Android Popover 120 | .platform-android { 121 | 122 | .popover { 123 | margin-top: -32px; 124 | background-color: $popover-bg-color-android; 125 | box-shadow: $popover-box-shadow-android; 126 | 127 | .item { 128 | border-color: $popover-bg-color-android; 129 | background-color: $popover-bg-color-android; 130 | color: #4d4d4d; 131 | } 132 | &.popover-bottom { 133 | margin-top: 32px; 134 | } 135 | } 136 | 137 | .popover-backdrop, 138 | .popover-backdrop.active { 139 | background-color: transparent; 140 | } 141 | } 142 | 143 | 144 | // disable clicks on all but the popover 145 | .popover-open { 146 | pointer-events: none; 147 | 148 | .popover, 149 | .popover-backdrop { 150 | pointer-events: auto; 151 | } 152 | // prevent clicks on popover when loading overlay is active though 153 | &.loading-active { 154 | .popover, 155 | .popover-backdrop { 156 | pointer-events: none; 157 | } 158 | } 159 | } 160 | 161 | 162 | // wider popover on larger viewports 163 | @media (min-width: $popover-large-break-point) { 164 | .popover { 165 | width: $popover-large-width; 166 | margin-left: -$popover-large-width / 2; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_popup.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Popups 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .popup-container { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | bottom: 0; 12 | right: 0; 13 | background: rgba(0,0,0,0); 14 | 15 | @include display-flex(); 16 | @include justify-content(center); 17 | @include align-items(center); 18 | 19 | z-index: $z-index-popup; 20 | 21 | // Start hidden 22 | visibility: hidden; 23 | &.popup-showing { 24 | visibility: visible; 25 | } 26 | 27 | &.popup-hidden .popup { 28 | @include animation-name(scaleOut); 29 | @include animation-duration($popup-leave-animation-duration); 30 | @include animation-timing-function(ease-in-out); 31 | @include animation-fill-mode(both); 32 | } 33 | 34 | &.active .popup { 35 | @include animation-name(superScaleIn); 36 | @include animation-duration($popup-enter-animation-duration); 37 | @include animation-timing-function(ease-in-out); 38 | @include animation-fill-mode(both); 39 | } 40 | 41 | .popup { 42 | width: $popup-width; 43 | max-width: 100%; 44 | max-height: 90%; 45 | 46 | border-radius: $popup-border-radius; 47 | background-color: $popup-background-color; 48 | 49 | @include display-flex(); 50 | @include flex-direction(column); 51 | } 52 | 53 | input, 54 | textarea { 55 | width: 100%; 56 | } 57 | } 58 | 59 | .popup-head { 60 | padding: 15px 10px; 61 | border-bottom: 1px solid #eee; 62 | text-align: center; 63 | } 64 | .popup-title { 65 | margin: 0; 66 | padding: 0; 67 | font-size: 15px; 68 | } 69 | .popup-sub-title { 70 | margin: 5px 0 0 0; 71 | padding: 0; 72 | font-weight: normal; 73 | font-size: 11px; 74 | } 75 | .popup-body { 76 | padding: 10px; 77 | overflow: auto; 78 | } 79 | 80 | .popup-buttons { 81 | @include display-flex(); 82 | @include flex-direction(row); 83 | padding: 10px; 84 | min-height: $popup-button-min-height + 20; 85 | 86 | .button { 87 | @include flex(1); 88 | display: block; 89 | min-height: $popup-button-min-height; 90 | border-radius: $popup-button-border-radius; 91 | line-height: $popup-button-line-height; 92 | 93 | margin-right: 5px; 94 | &:last-child { 95 | margin-right: 0px; 96 | } 97 | } 98 | } 99 | 100 | .popup-open { 101 | pointer-events: none; 102 | 103 | &.modal-open .modal { 104 | pointer-events: none; 105 | } 106 | 107 | .popup-backdrop, .popup { 108 | pointer-events: auto; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_progress.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Progress 4 | * -------------------------------------------------- 5 | */ 6 | 7 | progress { 8 | display: block; 9 | margin: $progress-margin; 10 | width: $progress-width; 11 | } 12 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_radio.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Radio Button Inputs 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-radio { 8 | padding: 0; 9 | 10 | &:hover { 11 | cursor: pointer; 12 | } 13 | } 14 | 15 | .item-radio .item-content { 16 | /* give some room to the right for the checkmark icon */ 17 | padding-right: $item-padding * 4; 18 | } 19 | 20 | .item-radio .radio-icon { 21 | /* checkmark icon will be hidden by default */ 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | z-index: $z-index-item-radio; 26 | visibility: hidden; 27 | padding: $item-padding - 2; 28 | height: 100%; 29 | font-size: 24px; 30 | } 31 | 32 | .item-radio input { 33 | /* hide any radio button inputs elements (the ugly circles) */ 34 | position: absolute; 35 | left: -9999px; 36 | 37 | &:checked + .radio-content .item-content { 38 | /* style the item content when its checked */ 39 | background: #f7f7f7; 40 | } 41 | 42 | &:checked + .radio-content .radio-icon { 43 | /* show the checkmark icon when its checked */ 44 | visibility: visible; 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_range.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Range 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .range input{ 8 | display: inline-block; 9 | overflow: hidden; 10 | margin-top: 5px; 11 | margin-bottom: 5px; 12 | padding-right: 2px; 13 | padding-left: 1px; 14 | width: auto; 15 | height: $range-slider-height + 15; 16 | outline: none; 17 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, $range-default-track-bg), color-stop(100%, $range-default-track-bg)); 18 | background: linear-gradient(to right, $range-default-track-bg 0%, $range-default-track-bg 100%); 19 | background-position: center; 20 | background-size: 99% $range-track-height; 21 | background-repeat: no-repeat; 22 | -webkit-appearance: none; 23 | 24 | &::-moz-focus-outer { 25 | /* hide the focus outline in Firefox */ 26 | border: 0; 27 | } 28 | 29 | &::-webkit-slider-thumb { 30 | position: relative; 31 | width: $range-slider-width; 32 | height: $range-slider-height; 33 | border-radius: $range-slider-border-radius; 34 | background-color: $toggle-handle-off-bg-color; 35 | box-shadow: $range-slider-box-shadow; 36 | cursor: pointer; 37 | -webkit-appearance: none; 38 | border: 0; 39 | } 40 | 41 | &::-webkit-slider-thumb:before{ 42 | /* what creates the colorful line on the left side of the slider */ 43 | position: absolute; 44 | top: ($range-slider-height / 2) - ($range-track-height / 2); 45 | left: -2001px; 46 | width: 2000px; 47 | height: $range-track-height; 48 | background: $dark; 49 | content: ' '; 50 | } 51 | 52 | &::-webkit-slider-thumb:after { 53 | /* create a larger (but hidden) hit area */ 54 | position: absolute; 55 | top: -15px; 56 | left: -15px; 57 | padding: 30px; 58 | content: ' '; 59 | //background: red; 60 | //opacity: .5; 61 | } 62 | &::-ms-fill-lower{ 63 | height: $range-track-height; 64 | background:$dark; 65 | } 66 | /* 67 | &::-ms-track{ 68 | background: transparent; 69 | border-color: transparent; 70 | border-width: 11px 0 16px; 71 | color:transparent; 72 | margin-top:20px; 73 | } 74 | &::-ms-thumb { 75 | width: $range-slider-width; 76 | height: $range-slider-height; 77 | border-radius: $range-slider-border-radius; 78 | background-color: $toggle-handle-off-bg-color; 79 | border-color:$toggle-handle-off-bg-color; 80 | box-shadow: $range-slider-box-shadow; 81 | margin-left:1px; 82 | margin-right:1px; 83 | outline:none; 84 | } 85 | &::-ms-fill-upper { 86 | height: $range-track-height; 87 | background:$range-default-track-bg; 88 | } 89 | */ 90 | } 91 | 92 | .range { 93 | @include display-flex(); 94 | @include align-items(center); 95 | padding: 2px 11px; 96 | 97 | &.range-light { 98 | input { @include range-style($range-light-track-bg); } 99 | } 100 | &.range-stable { 101 | input { @include range-style($range-stable-track-bg); } 102 | } 103 | &.range-positive { 104 | input { @include range-style($range-positive-track-bg); } 105 | } 106 | &.range-calm { 107 | input { @include range-style($range-calm-track-bg); } 108 | } 109 | &.range-balanced { 110 | input { @include range-style($range-balanced-track-bg); } 111 | } 112 | &.range-assertive { 113 | input { @include range-style($range-assertive-track-bg); } 114 | } 115 | &.range-energized { 116 | input { @include range-style($range-energized-track-bg); } 117 | } 118 | &.range-royal { 119 | input { @include range-style($range-royal-track-bg); } 120 | } 121 | &.range-dark { 122 | input { @include range-style($range-dark-track-bg); } 123 | } 124 | } 125 | 126 | .range .icon { 127 | @include flex(0); 128 | display: block; 129 | min-width: $range-icon-size; 130 | text-align: center; 131 | font-size: $range-icon-size; 132 | } 133 | 134 | .range input { 135 | @include flex(1); 136 | display: block; 137 | margin-right: 10px; 138 | margin-left: 10px; 139 | } 140 | 141 | .range-label { 142 | @include flex(0, 0, auto); 143 | display: block; 144 | white-space: nowrap; 145 | } 146 | 147 | .range-label:first-child { 148 | padding-left: 5px; 149 | } 150 | .range input + .range-label { 151 | padding-right: 5px; 152 | padding-left: 0; 153 | } 154 | 155 | // WP range height must be auto 156 | .platform-windowsphone{ 157 | .range input{ 158 | height:auto; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_refresher.scss: -------------------------------------------------------------------------------- 1 | 2 | // Scroll refresher (for pull to refresh) 3 | .scroll-refresher { 4 | position: absolute; 5 | top: -60px; 6 | right: 0; 7 | left: 0; 8 | overflow: hidden; 9 | margin: auto; 10 | height: 60px; 11 | .ionic-refresher-content { 12 | position: absolute; 13 | bottom: 15px; 14 | left: 0; 15 | width: 100%; 16 | color: $scroll-refresh-icon-color; 17 | text-align: center; 18 | 19 | font-size: 30px; 20 | 21 | .text-refreshing, 22 | .text-pulling { 23 | font-size: 16px; 24 | line-height: 16px; 25 | } 26 | &.ionic-refresher-with-text { 27 | bottom: 10px; 28 | } 29 | } 30 | 31 | .icon-refreshing, 32 | .icon-pulling { 33 | width: 100%; 34 | -webkit-backface-visibility: hidden; 35 | backface-visibility: hidden; 36 | -webkit-transform-style: preserve-3d; 37 | transform-style: preserve-3d; 38 | } 39 | .icon-pulling { 40 | @include animation-name(refresh-spin-back); 41 | @include animation-duration(200ms); 42 | @include animation-timing-function(linear); 43 | @include animation-fill-mode(none); 44 | -webkit-transform: translate3d(0,0,0) rotate(0deg); 45 | transform: translate3d(0,0,0) rotate(0deg); 46 | } 47 | .icon-refreshing, 48 | .text-refreshing { 49 | display: none; 50 | } 51 | .icon-refreshing { 52 | @include animation-duration(1.5s); 53 | } 54 | 55 | &.active { 56 | .icon-pulling:not(.pulling-rotation-disabled) { 57 | @include animation-name(refresh-spin); 58 | -webkit-transform: translate3d(0,0,0) rotate(-180deg); 59 | transform: translate3d(0,0,0) rotate(-180deg); 60 | } 61 | &.refreshing { 62 | @include transition(-webkit-transform .2s); 63 | @include transition(transform .2s); 64 | -webkit-transform: scale(1,1); 65 | transform: scale(1,1); 66 | 67 | .icon-pulling, 68 | .text-pulling { 69 | display: none; 70 | } 71 | .icon-refreshing, 72 | .text-refreshing { 73 | display: block; 74 | } 75 | &.refreshing-tail { 76 | -webkit-transform: scale(0,0); 77 | transform: scale(0,0); 78 | } 79 | } 80 | } 81 | } 82 | .overflow-scroll > .scroll{ 83 | &.overscroll{ 84 | position:fixed; 85 | right: 0; 86 | left: 0; 87 | } 88 | -webkit-overflow-scrolling:touch; 89 | width:100%; 90 | } 91 | 92 | .overflow-scroll.padding > .scroll.overscroll{ 93 | padding: 10px; 94 | } 95 | @-webkit-keyframes refresh-spin { 96 | 0% { -webkit-transform: translate3d(0,0,0) rotate(0); } 97 | 100% { -webkit-transform: translate3d(0,0,0) rotate(180deg); } 98 | } 99 | 100 | @keyframes refresh-spin { 101 | 0% { transform: translate3d(0,0,0) rotate(0); } 102 | 100% { transform: translate3d(0,0,0) rotate(180deg); } 103 | } 104 | 105 | @-webkit-keyframes refresh-spin-back { 106 | 0% { -webkit-transform: translate3d(0,0,0) rotate(180deg); } 107 | 100% { -webkit-transform: translate3d(0,0,0) rotate(0); } 108 | } 109 | 110 | @keyframes refresh-spin-back { 111 | 0% { transform: translate3d(0,0,0) rotate(180deg); } 112 | 100% { transform: translate3d(0,0,0) rotate(0); } 113 | } 114 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_select.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Select 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-select { 8 | position: relative; 9 | 10 | select { 11 | @include appearance(none); 12 | position: absolute; 13 | top: 0; 14 | bottom: 0; 15 | right: 0; 16 | padding: 0 ($item-padding * 3) 0 $item-padding; 17 | max-width: 65%; 18 | 19 | border: none; 20 | background: $item-default-bg; 21 | color: #333; 22 | 23 | // hack to hide default dropdown arrow in FF 24 | text-indent: .01px; 25 | text-overflow: ''; 26 | 27 | white-space: nowrap; 28 | font-size: $font-size-base; 29 | 30 | cursor: pointer; 31 | direction: rtl; // right align the select text 32 | } 33 | 34 | select::-ms-expand { 35 | // hide default dropdown arrow in IE 36 | display: none; 37 | } 38 | 39 | option { 40 | direction: ltr; 41 | } 42 | 43 | &:after { 44 | position: absolute; 45 | top: 50%; 46 | right: $item-padding; 47 | margin-top: -3px; 48 | width: 0; 49 | height: 0; 50 | border-top: 5px solid; 51 | border-right: 5px solid rgba(0, 0, 0, 0); 52 | border-left: 5px solid rgba(0, 0, 0, 0); 53 | color: #999; 54 | content: ""; 55 | pointer-events: none; 56 | } 57 | &.item-light { 58 | select{ 59 | background:$item-light-bg; 60 | color:$item-light-text; 61 | } 62 | } 63 | &.item-stable { 64 | select{ 65 | background:$item-stable-bg; 66 | color:$item-stable-text; 67 | } 68 | &:after, .input-label{ 69 | color:darken($item-stable-border,30%); 70 | } 71 | } 72 | &.item-positive { 73 | select{ 74 | background:$item-positive-bg; 75 | color:$item-positive-text; 76 | } 77 | &:after, .input-label{ 78 | color:$item-positive-text; 79 | } 80 | } 81 | &.item-calm { 82 | select{ 83 | background:$item-calm-bg; 84 | color:$item-calm-text; 85 | } 86 | &:after, .input-label{ 87 | color:$item-calm-text; 88 | } 89 | } 90 | &.item-assertive { 91 | select{ 92 | background:$item-assertive-bg; 93 | color:$item-assertive-text; 94 | } 95 | &:after, .input-label{ 96 | color:$item-assertive-text; 97 | } 98 | } 99 | &.item-balanced { 100 | select{ 101 | background:$item-balanced-bg; 102 | color:$item-balanced-text; 103 | } 104 | &:after, .input-label{ 105 | color:$item-balanced-text; 106 | } 107 | } 108 | &.item-energized { 109 | select{ 110 | background:$item-energized-bg; 111 | color:$item-energized-text; 112 | } 113 | &:after, .input-label{ 114 | color:$item-energized-text; 115 | } 116 | } 117 | &.item-royal { 118 | select{ 119 | background:$item-royal-bg; 120 | color:$item-royal-text; 121 | } 122 | &:after, .input-label{ 123 | color:$item-royal-text; 124 | } 125 | } 126 | &.item-dark { 127 | select{ 128 | background:$item-dark-bg; 129 | color:$item-dark-text; 130 | } 131 | &:after, .input-label{ 132 | color:$item-dark-text; 133 | } 134 | } 135 | } 136 | 137 | select { 138 | &[multiple], 139 | &[size] { 140 | height: auto; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_slide-box.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Slide Box 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .slider { 8 | position: relative; 9 | visibility: hidden; 10 | // Make sure items don't scroll over ever 11 | overflow: hidden; 12 | } 13 | 14 | .slider-slides { 15 | position: relative; 16 | height: 100%; 17 | } 18 | 19 | .slider-slide { 20 | position: relative; 21 | display: block; 22 | float: left; 23 | width: 100%; 24 | height: 100%; 25 | vertical-align: top; 26 | } 27 | 28 | .slider-slide-image { 29 | > img { 30 | width: 100%; 31 | } 32 | } 33 | 34 | .slider-pager { 35 | position: absolute; 36 | bottom: 20px; 37 | z-index: $z-index-slider-pager; 38 | width: 100%; 39 | height: 15px; 40 | text-align: center; 41 | 42 | .slider-pager-page { 43 | display: inline-block; 44 | margin: 0px 3px; 45 | width: 15px; 46 | color: #000; 47 | text-decoration: none; 48 | 49 | opacity: 0.3; 50 | 51 | &.active { 52 | @include transition(opacity 0.4s ease-in); 53 | opacity: 1; 54 | } 55 | } 56 | } 57 | 58 | //Disable animate service animations 59 | .slider-slide, 60 | .slider-pager-page { 61 | &.ng-enter, 62 | &.ng-leave, 63 | &.ng-animate { 64 | -webkit-transition: none !important; 65 | transition: none !important; 66 | } 67 | &.ng-animate { 68 | -webkit-animation: none 0s; 69 | animation: none 0s; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_spinner.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Spinners 3 | * -------------------------------------------------- 4 | */ 5 | 6 | .spinner { 7 | svg { 8 | width: $spinner-width; 9 | height: $spinner-height; 10 | } 11 | 12 | stroke: $spinner-default-stroke; 13 | fill: $spinner-default-fill; 14 | 15 | &.spinner-light { 16 | stroke: $spinner-light-stroke; 17 | fill: $spinner-light-fill; 18 | } 19 | &.spinner-stable { 20 | stroke: $spinner-stable-stroke; 21 | fill: $spinner-stable-fill; 22 | } 23 | &.spinner-positive { 24 | stroke: $spinner-positive-stroke; 25 | fill: $spinner-positive-fill; 26 | } 27 | &.spinner-calm { 28 | stroke: $spinner-calm-stroke; 29 | fill: $spinner-calm-fill; 30 | } 31 | &.spinner-balanced { 32 | stroke: $spinner-balanced-stroke; 33 | fill: $spinner-balanced-fill; 34 | } 35 | &.spinner-assertive { 36 | stroke: $spinner-assertive-stroke; 37 | fill: $spinner-assertive-fill; 38 | } 39 | &.spinner-energized { 40 | stroke: $spinner-energized-stroke; 41 | fill: $spinner-energized-fill; 42 | } 43 | &.spinner-royal { 44 | stroke: $spinner-royal-stroke; 45 | fill: $spinner-royal-fill; 46 | } 47 | &.spinner-dark { 48 | stroke: $spinner-dark-stroke; 49 | fill: $spinner-dark-fill; 50 | } 51 | } 52 | 53 | .spinner-android { 54 | stroke: #4b8bf4; 55 | } 56 | 57 | .spinner-ios, 58 | .spinner-ios-small { 59 | stroke: #69717d; 60 | } 61 | 62 | .spinner-spiral { 63 | .stop1 { 64 | stop-color: $spinner-light-fill; 65 | stop-opacity: 0; 66 | } 67 | 68 | &.spinner-light { 69 | .stop1 { 70 | stop-color: $spinner-default-fill; 71 | } 72 | .stop2 { 73 | stop-color: $spinner-light-fill; 74 | } 75 | } 76 | &.spinner-stable .stop2 { 77 | stop-color: $spinner-stable-fill; 78 | } 79 | &.spinner-positive .stop2 { 80 | stop-color: $spinner-positive-fill; 81 | } 82 | &.spinner-calm .stop2 { 83 | stop-color: $spinner-calm-fill; 84 | } 85 | &.spinner-balanced .stop2 { 86 | stop-color: $spinner-balanced-fill; 87 | } 88 | &.spinner-assertive .stop2 { 89 | stop-color: $spinner-assertive-fill; 90 | } 91 | &.spinner-energized .stop2 { 92 | stop-color: $spinner-energized-fill; 93 | } 94 | &.spinner-royal .stop2 { 95 | stop-color: $spinner-royal-fill; 96 | } 97 | &.spinner-dark .stop2 { 98 | stop-color: $spinner-dark-fill; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_toggle.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Toggle 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-toggle { 8 | pointer-events: none; 9 | } 10 | 11 | .toggle { 12 | // set the color defaults 13 | @include toggle-style($toggle-on-default-border, $toggle-on-default-bg); 14 | 15 | position: relative; 16 | display: inline-block; 17 | pointer-events: auto; 18 | margin: -$toggle-hit-area-expansion; 19 | padding: $toggle-hit-area-expansion; 20 | 21 | &.dragging { 22 | .handle { 23 | background-color: $toggle-handle-dragging-bg-color !important; 24 | } 25 | } 26 | 27 | } 28 | 29 | .toggle { 30 | &.toggle-light { 31 | @include toggle-style($toggle-on-light-border, $toggle-on-light-bg); 32 | } 33 | &.toggle-stable { 34 | @include toggle-style($toggle-on-stable-border, $toggle-on-stable-bg); 35 | } 36 | &.toggle-positive { 37 | @include toggle-style($toggle-on-positive-border, $toggle-on-positive-bg); 38 | } 39 | &.toggle-calm { 40 | @include toggle-style($toggle-on-calm-border, $toggle-on-calm-bg); 41 | } 42 | &.toggle-assertive { 43 | @include toggle-style($toggle-on-assertive-border, $toggle-on-assertive-bg); 44 | } 45 | &.toggle-balanced { 46 | @include toggle-style($toggle-on-balanced-border, $toggle-on-balanced-bg); 47 | } 48 | &.toggle-energized { 49 | @include toggle-style($toggle-on-energized-border, $toggle-on-energized-bg); 50 | } 51 | &.toggle-royal { 52 | @include toggle-style($toggle-on-royal-border, $toggle-on-royal-bg); 53 | } 54 | &.toggle-dark { 55 | @include toggle-style($toggle-on-dark-border, $toggle-on-dark-bg); 56 | } 57 | } 58 | 59 | .toggle input { 60 | // hide the actual input checkbox 61 | display: none; 62 | } 63 | 64 | /* the track appearance when the toggle is "off" */ 65 | .toggle .track { 66 | @include transition-timing-function(ease-in-out); 67 | @include transition-duration($toggle-transition-duration); 68 | @include transition-property((background-color, border)); 69 | 70 | display: inline-block; 71 | box-sizing: border-box; 72 | width: $toggle-width; 73 | height: $toggle-height; 74 | border: solid $toggle-border-width $toggle-off-border-color; 75 | border-radius: $toggle-border-radius; 76 | background-color: $toggle-off-bg-color; 77 | content: ' '; 78 | cursor: pointer; 79 | pointer-events: none; 80 | } 81 | 82 | /* Fix to avoid background color bleeding */ 83 | /* (occurred on (at least) Android 4.2, Asus MeMO Pad HD7 ME173X) */ 84 | .platform-android4_2 .toggle .track { 85 | -webkit-background-clip: padding-box; 86 | } 87 | 88 | /* the handle (circle) thats inside the toggle's track area */ 89 | /* also the handle's appearance when it is "off" */ 90 | .toggle .handle { 91 | @include transition($toggle-transition-duration cubic-bezier(0, 1.1, 1, 1.1)); 92 | @include transition-property((background-color, transform)); 93 | position: absolute; 94 | display: block; 95 | width: $toggle-handle-width; 96 | height: $toggle-handle-height; 97 | border-radius: $toggle-handle-radius; 98 | background-color: $toggle-handle-off-bg-color; 99 | top: $toggle-border-width + $toggle-hit-area-expansion; 100 | left: $toggle-border-width + $toggle-hit-area-expansion; 101 | box-shadow: 0 2px 7px rgba(0,0,0,.35), 0 1px 1px rgba(0,0,0,.15); 102 | 103 | &:before { 104 | // used to create a larger (but hidden) hit area to slide the handle 105 | position: absolute; 106 | top: -4px; 107 | left: ( ($toggle-handle-width / 2) * -1) - 8; 108 | padding: ($toggle-handle-height / 2) + 5 ($toggle-handle-width + 7); 109 | content: " "; 110 | } 111 | } 112 | 113 | .toggle input:checked + .track .handle { 114 | // the handle when the toggle is "on" 115 | @include translate3d($toggle-width - $toggle-handle-width - ($toggle-border-width * 2), 0, 0); 116 | background-color: $toggle-handle-on-bg-color; 117 | } 118 | 119 | .item-toggle.active { 120 | box-shadow: none; 121 | } 122 | 123 | .item-toggle, 124 | .item-toggle.item-complex .item-content { 125 | // make sure list item content have enough padding on right to fit the toggle 126 | padding-right: ($item-padding * 3) + $toggle-width; 127 | } 128 | 129 | .item-toggle.item-complex { 130 | padding-right: 0; 131 | } 132 | 133 | .item-toggle .toggle { 134 | // position the toggle to the right within a list item 135 | position: absolute; 136 | top: ($item-padding / 2) + 2; 137 | right: $item-padding; 138 | z-index: $z-index-item-toggle; 139 | } 140 | 141 | .toggle input:disabled + .track { 142 | opacity: .6; 143 | } 144 | 145 | .toggle-small { 146 | 147 | .track { 148 | border: 0; 149 | width: 34px; 150 | height: 15px; 151 | background: #9e9e9e; 152 | } 153 | input:checked + .track { 154 | background: rgba(0,150,137,.5); 155 | } 156 | .handle { 157 | top: 2px; 158 | left: 4px; 159 | width: 21px; 160 | height: 21px; 161 | box-shadow: 0 2px 5px rgba(0,0,0,.25); 162 | } 163 | input:checked + .track .handle { 164 | @include translate3d(16px, 0, 0); 165 | background: rgb(0,150,137); 166 | } 167 | &.item-toggle .toggle { 168 | top: 19px; 169 | } 170 | 171 | .toggle-light { 172 | @include toggle-small-style($toggle-on-light-bg); 173 | } 174 | .toggle-stable { 175 | @include toggle-small-style($toggle-on-stable-bg); 176 | } 177 | .toggle-positive { 178 | @include toggle-small-style($toggle-on-positive-bg); 179 | } 180 | .toggle-calm { 181 | @include toggle-small-style($toggle-on-calm-bg); 182 | } 183 | .toggle-assertive { 184 | @include toggle-small-style($toggle-on-assertive-bg); 185 | } 186 | .toggle-balanced { 187 | @include toggle-small-style($toggle-on-balanced-bg); 188 | } 189 | .toggle-energized { 190 | @include toggle-small-style($toggle-on-energized-bg); 191 | } 192 | .toggle-royal { 193 | @include toggle-small-style($toggle-on-royal-bg); 194 | } 195 | .toggle-dark { 196 | @include toggle-small-style($toggle-on-dark-bg); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_transitions.scss: -------------------------------------------------------------------------------- 1 | 2 | // iOS View Transitions 3 | // ------------------------------- 4 | 5 | $ios-transition-duration: 500ms !default; 6 | $ios-transition-timing-function: cubic-bezier(.36, .66, .04, 1) !default; 7 | $ios-transition-container-bg-color: #000 !default; 8 | 9 | 10 | [nav-view-transition="ios"] { 11 | 12 | [nav-view="entering"], 13 | [nav-view="leaving"] { 14 | @include transition-duration( $ios-transition-duration ); 15 | @include transition-timing-function( $ios-transition-timing-function ); 16 | -webkit-transition-property: opacity, -webkit-transform, box-shadow; 17 | transition-property: opacity, transform, box-shadow; 18 | } 19 | 20 | &[nav-view-direction="forward"], 21 | &[nav-view-direction="back"] { 22 | background-color: $ios-transition-container-bg-color; 23 | } 24 | 25 | [nav-view="active"], 26 | &[nav-view-direction="forward"] [nav-view="entering"], 27 | &[nav-view-direction="back"] [nav-view="leaving"] { 28 | z-index: $z-index-view-above; 29 | } 30 | 31 | &[nav-view-direction="back"] [nav-view="entering"], 32 | &[nav-view-direction="forward"] [nav-view="leaving"] { 33 | z-index: $z-index-view-below; 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | // iOS Nav Bar Transitions 41 | // ------------------------------- 42 | 43 | [nav-bar-transition="ios"] { 44 | 45 | .title, 46 | .buttons, 47 | .back-text { 48 | @include transition-duration( $ios-transition-duration ); 49 | @include transition-timing-function( $ios-transition-timing-function ); 50 | -webkit-transition-property: opacity, -webkit-transform; 51 | transition-property: opacity, transform; 52 | } 53 | 54 | [nav-bar="active"], 55 | [nav-bar="entering"] { 56 | z-index: $z-index-bar-above; 57 | 58 | .bar { 59 | background: transparent; 60 | } 61 | } 62 | 63 | [nav-bar="cached"] { 64 | display: block; 65 | 66 | .header-item { 67 | display: none; 68 | } 69 | } 70 | 71 | } 72 | 73 | 74 | 75 | // Android View Transitions 76 | // ------------------------------- 77 | 78 | $android-transition-duration: 200ms !default; 79 | $android-transition-timing-function: cubic-bezier(0.4, 0.6, 0.2, 1) !default; 80 | 81 | 82 | [nav-view-transition="android"] { 83 | 84 | [nav-view="entering"], 85 | [nav-view="leaving"] { 86 | @include transition-duration( $android-transition-duration ); 87 | @include transition-timing-function( $android-transition-timing-function ); 88 | -webkit-transition-property: -webkit-transform; 89 | transition-property: transform; 90 | } 91 | 92 | [nav-view="active"], 93 | &[nav-view-direction="forward"] [nav-view="entering"], 94 | &[nav-view-direction="back"] [nav-view="leaving"] { 95 | z-index: $z-index-view-above; 96 | } 97 | 98 | &[nav-view-direction="back"] [nav-view="entering"], 99 | &[nav-view-direction="forward"] [nav-view="leaving"] { 100 | z-index: $z-index-view-below; 101 | } 102 | 103 | } 104 | 105 | 106 | 107 | // Android Nav Bar Transitions 108 | // ------------------------------- 109 | 110 | [nav-bar-transition="android"] { 111 | 112 | .title, 113 | .buttons { 114 | @include transition-duration( $android-transition-duration ); 115 | @include transition-timing-function( $android-transition-timing-function ); 116 | -webkit-transition-property: opacity; 117 | transition-property: opacity; 118 | } 119 | 120 | [nav-bar="active"], 121 | [nav-bar="entering"] { 122 | z-index: $z-index-bar-above; 123 | 124 | .bar { 125 | background: transparent; 126 | } 127 | } 128 | 129 | [nav-bar="cached"] { 130 | display: block; 131 | 132 | .header-item { 133 | display: none; 134 | } 135 | } 136 | 137 | } 138 | 139 | 140 | 141 | // Nav Swipe 142 | // ------------------------------- 143 | 144 | [nav-swipe="fast"] { 145 | [nav-view], 146 | .title, 147 | .buttons, 148 | .back-text { 149 | @include transition-duration(50ms); 150 | @include transition-timing-function(linear); 151 | } 152 | } 153 | 154 | [nav-swipe="slow"] { 155 | [nav-view], 156 | .title, 157 | .buttons, 158 | .back-text { 159 | @include transition-duration(160ms); 160 | @include transition-timing-function(linear); 161 | } 162 | } 163 | 164 | 165 | 166 | // Transition Settings 167 | // ------------------------------- 168 | 169 | [nav-view="cached"], 170 | [nav-bar="cached"] { 171 | display: none; 172 | } 173 | 174 | [nav-view="stage"] { 175 | opacity: 0; 176 | @include transition-duration( 0 ); 177 | } 178 | 179 | [nav-bar="stage"] { 180 | .title, 181 | .buttons, 182 | .back-text { 183 | position: absolute; 184 | opacity: 0; 185 | @include transition-duration(0s); 186 | } 187 | } 188 | 189 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/_type.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Typography 4 | * -------------------------------------------------- 5 | */ 6 | 7 | 8 | // Body text 9 | // ------------------------- 10 | 11 | p { 12 | margin: 0 0 ($line-height-computed / 2); 13 | } 14 | 15 | 16 | // Emphasis & misc 17 | // ------------------------- 18 | 19 | small { font-size: 85%; } 20 | cite { font-style: normal; } 21 | 22 | 23 | // Alignment 24 | // ------------------------- 25 | 26 | .text-left { text-align: left; } 27 | .text-right { text-align: right; } 28 | .text-center { text-align: center; } 29 | 30 | 31 | // Headings 32 | // ------------------------- 33 | 34 | h1, h2, h3, h4, h5, h6, 35 | .h1, .h2, .h3, .h4, .h5, .h6 { 36 | color: $base-color; 37 | font-weight: $headings-font-weight; 38 | font-family: $headings-font-family; 39 | line-height: $headings-line-height; 40 | 41 | small { 42 | font-weight: normal; 43 | line-height: 1; 44 | } 45 | } 46 | 47 | h1, .h1, 48 | h2, .h2, 49 | h3, .h3 { 50 | margin-top: $line-height-computed; 51 | margin-bottom: ($line-height-computed / 2); 52 | 53 | &:first-child { 54 | margin-top: 0; 55 | } 56 | 57 | + h1, + .h1, 58 | + h2, + .h2, 59 | + h3, + .h3 { 60 | margin-top: ($line-height-computed / 2); 61 | } 62 | } 63 | 64 | h4, .h4, 65 | h5, .h5, 66 | h6, .h6 { 67 | margin-top: ($line-height-computed / 2); 68 | margin-bottom: ($line-height-computed / 2); 69 | } 70 | 71 | h1, .h1 { font-size: floor($font-size-base * 2.60); } // ~36px 72 | h2, .h2 { font-size: floor($font-size-base * 2.15); } // ~30px 73 | h3, .h3 { font-size: ceil($font-size-base * 1.70); } // ~24px 74 | h4, .h4 { font-size: ceil($font-size-base * 1.25); } // ~18px 75 | h5, .h5 { font-size: $font-size-base; } 76 | h6, .h6 { font-size: ceil($font-size-base * 0.85); } // ~12px 77 | 78 | h1 small, .h1 small { font-size: ceil($font-size-base * 1.70); } // ~24px 79 | h2 small, .h2 small { font-size: ceil($font-size-base * 1.25); } // ~18px 80 | h3 small, .h3 small, 81 | h4 small, .h4 small { font-size: $font-size-base; } 82 | 83 | 84 | // Description Lists 85 | // ------------------------- 86 | 87 | dl { 88 | margin-bottom: $line-height-computed; 89 | } 90 | dt, 91 | dd { 92 | line-height: $line-height-base; 93 | } 94 | dt { 95 | font-weight: bold; 96 | } 97 | 98 | 99 | // Blockquotes 100 | // ------------------------- 101 | 102 | blockquote { 103 | margin: 0 0 $line-height-computed; 104 | padding: ($line-height-computed / 2) $line-height-computed; 105 | border-left: 5px solid gray; 106 | 107 | p { 108 | font-weight: 300; 109 | font-size: ($font-size-base * 1.25); 110 | line-height: 1.25; 111 | } 112 | 113 | p:last-child { 114 | margin-bottom: 0; 115 | } 116 | 117 | small { 118 | display: block; 119 | line-height: $line-height-base; 120 | &:before { 121 | content: '\2014 \00A0';// EM DASH, NBSP; 122 | } 123 | } 124 | } 125 | 126 | 127 | // Quotes 128 | // ------------------------- 129 | 130 | q:before, 131 | q:after, 132 | blockquote:before, 133 | blockquote:after { 134 | content: ""; 135 | } 136 | 137 | 138 | // Addresses 139 | // ------------------------- 140 | 141 | address { 142 | display: block; 143 | margin-bottom: $line-height-computed; 144 | font-style: normal; 145 | line-height: $line-height-base; 146 | } 147 | 148 | 149 | // Links 150 | // ------------------------- 151 | a { 152 | color: $link-color; 153 | } 154 | 155 | a.subdued { 156 | padding-right: 10px; 157 | color: #888; 158 | text-decoration: none; 159 | 160 | &:hover { 161 | text-decoration: none; 162 | } 163 | &:last-child { 164 | padding-right: 0; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/ionic.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import 4 | // Ionicons 5 | "ionicons/ionicons.scss", 6 | 7 | // Variables 8 | "mixins", 9 | "variables", 10 | 11 | // Base 12 | "reset", 13 | "scaffolding", 14 | "type", 15 | 16 | // Components 17 | "action-sheet", 18 | "backdrop", 19 | "bar", 20 | "tabs", 21 | "menu", 22 | "modal", 23 | "popover", 24 | "popup", 25 | "loading", 26 | "items", 27 | "list", 28 | "badge", 29 | "slide-box", 30 | "slides", 31 | "refresher", 32 | "spinner", 33 | 34 | // Forms 35 | "form", 36 | "checkbox", 37 | "toggle", 38 | "radio", 39 | "range", 40 | "select", 41 | "progress", 42 | 43 | // Buttons 44 | "button", 45 | "button-bar", 46 | 47 | // Util 48 | "grid", 49 | "util", 50 | "platform", 51 | 52 | // Animations 53 | "animations", 54 | "transitions"; 55 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/ionicons/_ionicons-font.scss: -------------------------------------------------------------------------------- 1 | // Ionicons Font Path 2 | // -------------------------- 3 | 4 | @font-face { 5 | font-family: $ionicons-font-family; 6 | src:url("#{$ionicons-font-path}/ionicons.eot?v=#{$ionicons-version}"); 7 | src:url("#{$ionicons-font-path}/ionicons.eot?v=#{$ionicons-version}#iefix") format("embedded-opentype"), 8 | url("#{$ionicons-font-path}/ionicons.ttf?v=#{$ionicons-version}") format("truetype"), 9 | url("#{$ionicons-font-path}/ionicons.woff?v=#{$ionicons-version}") format("woff"), 10 | url("#{$ionicons-font-path}/ionicons.woff") format("woff"), /* for WP8 */ 11 | url("#{$ionicons-font-path}/ionicons.svg?v=#{$ionicons-version}#Ionicons") format("svg"); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | .ion { 17 | display: inline-block; 18 | font-family: $ionicons-font-family; 19 | speak: none; 20 | font-style: normal; 21 | font-weight: normal; 22 | font-variant: normal; 23 | text-transform: none; 24 | text-rendering: auto; 25 | line-height: 1; 26 | -webkit-font-smoothing: antialiased; 27 | -moz-osx-font-smoothing: grayscale; 28 | } 29 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/ionicons/ionicons.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import "ionicons-variables"; 3 | /*! 4 | Ionicons, v2.0.1 5 | Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ 6 | https://twitter.com/benjsperry https://twitter.com/ionicframework 7 | MIT License: https://github.com/driftyco/ionicons 8 | 9 | Android-style icons originally built by Google’s 10 | Material Design Icons: https://github.com/google/material-design-icons 11 | used under CC BY http://creativecommons.org/licenses/by/4.0/ 12 | Modified icons to fit ionicon’s grid from original. 13 | */ 14 | 15 | @import "ionicons-font"; 16 | @import "ionicons-icons"; 17 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/scss/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.5.0-alpha", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "commonjs", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "removeComments": true, 9 | "noLib": false, 10 | "preserveConstEnums": true, 11 | "suppressImplicitAnyIndexErrors": true 12 | }, 13 | "filesGlob": [ 14 | "./**/*.ts", 15 | "!./node_modules/**/*.ts" 16 | ], 17 | "files": [] 18 | } 19 | -------------------------------------------------------------------------------- /Mobile App Bootstrap/www/lib/ionic/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.3.1", 3 | "codename": "el-salvador", 4 | "date": "2016-05-12" 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mobile App Bootstrap: Objective-C preview 2 | [![Community Supported](https://img.shields.io/badge/Support%20Level-Community%20Supported-457387.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) 3 | 4 | Provides an early look at example code for how to embed Tableau vizzes inside of a native iOS app. 5 | 6 | ## Prerequisites 7 | This objective-c version of the Mobile App Bootstrap only supports iOS. 8 | 9 | ## Installation 10 | 11 | Hopefully straightforward: 12 | 13 | 1. Install Xcode 14 | 2. Download the code for the app from this [repository](https://github.com/tableau/mobile-app-bootstrap) 15 | 16 | ## Starting the Tableau Sample App 17 | 18 | 1. Open `Mobile App Bootstrap.xcodeproj` in Xcode 19 | 2. Choose/click "Run" 20 | 21 | This should run the app in a simulator, depending on the scheme set in Xcode. 22 | 23 | In order to run on a physical device, you'll need to adjust the Team signing profile in the Xcode project. From the Xcode project navigator, click the Mobile App Bootstrap project. Within the project info, click the Mobile App Bootstrap target, choose the General tab, and set a Team in the Signing section. This starts to get into app distribution, and Apple has extensive documentation with both a [Quick Start](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppStoreDistributionTutorial/Introduction/Introduction.html) and a [more detailed guide](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/Introduction/Introduction.html). 24 | 25 | 26 | # Project Layout 27 | 28 | The app is meant to be as simple as possible: a UITabBarController with 4 UIViewControllers, each containing a WKWebView. The UIViewControllers are separated into their own classes in this sample. The Home WebView opens a local web page bundled in the app, while the other 3 WebViews open visualizations hosted on Tableau Public. 29 | 30 | Tableau Connected Clients are used, with a modified version of the Connected Clients library. Once this sample app is farther along (see Limitations, below), the modifications to the Connected Clients library will be submitted to its repo. 31 | 32 | ## Customization Points 33 | 34 | `AppDelegate.m` 35 | Change `https://public.tableau.com` to your internal server. 36 | 37 | `Viz1ViewController.m`, `Viz2ViewController.m`, `Viz3ViewController.m` 38 | Change the viz URLs to point to your own visualizations. Consider including the URL query parameters in the sample URLs, described in more detail in comments in `Viz1ViewController.m`. 39 | 40 | `www/index.html` 41 | Modify to suit. 42 | 43 | # Limitations 44 | 45 | This code is strictly an early look, and is sadly far from complete. 46 | 47 | * Links within WebViews 48 | Tapping a link in one of these WebViews will follow the link...but there isn't a Back button included with this sample. 49 | 50 | * Connected Clients 51 | Connected Clients are implemented, via a simple Token Manager. Unfortunately it doesn't yet perform re-authentication. So while it will keep the user signed in between app runs and device reboots, it will timeout based on Tableau Server's setting. On Server, this is configuralbe via its .yml file: 52 | `wgserver.session.idle_limit: 240` 53 | That value is in minutes, and affects all clients: Web, Desktop, and Mobile. 54 | 55 | * Sign In on Phone 56 | The Sign In experience on a phone is, charitably: nonoptimal. There's a way to inject some CSS into the WebView to make things lok nicer, via some JavaScript: 57 | `"var styleNode = document.createElement('style'); styleNode.type = 'text/css'; styleNode.innerHTML = ' .tb-login{min-width:0 !important; width: " + width + "px; } .tb-login-site-picker-container {margin-left: 50px; margin-right: 50px; } '; document.getElementsByTagName('head')[0].appendChild(styleNode);"` 58 | WebView has `EvaluateJavascript` for passing this in. Sadly, the sample code doesn't yet implement this. 59 | 60 | * Sign In approach 61 | In this sample, Sign In happen inside the same viz as the WebView...this might need to be broken out in a separate WebView that only processes Sign In. 62 | 63 | # Future Plans 64 | 65 | * Address the Limitations above 66 | 67 | * Anything else you'd like to see? Please get in touch with us through the [Tableau Developer Community](developer.tableau.com)! 68 | 69 | 70 | # Support 71 | 72 | This collection is supported by the community and not officially 'blessed' by Tableau Engineering or Support. What does that mean? We didn't have a QA team test it. It's a tool for learning how to embed vizzes inside a mobile application. You should not expect that there are 0 bugs. 73 | 74 | If you have problems getting it to work, feel free to email us with questions, but we can't promise quick responses. The [Tableau Developer Community](developer.tableau.com) is also a great resource if you need help. 75 | 76 | A standard disclaimer: mobile-app-bootstrap is made available AS-IS with no support and no warranty whatsoever. Despite efforts to write good and useful code there may be bugs that cause unexpected and undesirable behavior. The software is strictly “use at your own risk.” 77 | 78 | The good news: This is intended to be a self-service tool. You are free to modify it in any way to meet your needs. 79 | 80 | 81 | -------------------------------------------------------------------------------- /mobile-connected-client/README.md: -------------------------------------------------------------------------------- 1 | # Mobile Connected Client Plugin 2 | 3 | The Connected Client Plugin is an Objective-C library written to accompany the [Mobile App Bootstrap](https://github.com/tableau/mobile-app-bootstrap), 4 | but is also useful as a stand-alone class. It helps authenticate a user and keep them signed in using long-lived tokens. It can be used in the sample [Mobile App Bootstrap](https://github.com/tableau/mobile-app-bootstrap) or in another native mobile app. 5 | 6 | As a plugin, it performs three authentication-related tasks: 7 | 8 | 1. Check if the user is signed in. This function should always be called before trying to load Tableau content. 9 | `TableauOAuth.checkSignInStatus()` 10 | 2. Manage long-lived tokens for a session. Long-lived tokens [must be enabled](https://onlinehelp.tableau.com/current/server/en-us/devices_connected_credentials.htm) on your server. 11 | `TableauOAuth.requestOAuthTokens()` 12 | 3. Sign out the user. 13 | `TableauOAuth.signOut()` 14 | 15 | 16 | ## Check Sign In Flow 17 | ``` 18 | checkSignInStatus 19 | | 20 | Is the session expired? 21 | | 22 | __________________________ 23 | | | 24 | NO YES 25 | User is signed in. | 26 | Does the app have tokens? 27 | | 28 | __________________________ 29 | | | 30 | YES NO 31 | | User is NOT signed in. 32 | Can they be refreshed? 33 | | 34 | ___________________ 35 | | | 36 | YES NO 37 | User is signed in. User is NOT signed in. 38 | 39 | ``` 40 | 41 | ## Long-lived tokens 42 | 43 | The plugin completes four main tasks in order to handle long-lived tokens. 44 | 45 | 1. Get initial access/refresh tokens 46 | 2. Store in iOS Keychain 47 | 3. Refresh access token if expired 48 | 4. Revoke refresh and access token on sign out 49 | 50 | ## Support 51 | 52 | This collection is not officially 'blessed' by Tableau Engineering or Support. What does that mean? We didn't have a QA team test it. It's a tool for learning how to authenticate to Tableau Servers and stay signed-in using long-lived tokens. You should not expect that there are 0 bugs. If you have problems getting it to work, feel free to email us with questions, but we can't promise quick responses. 53 | 54 | A standard disclaimer: mobile-connected-client is made available AS-IS with no support and no warranty whatsoever. Despite efforts to write good and useful code there may be bugs that cause unexpected and undesirable behavior. The software is strictly “use at your own risk.” 55 | The good news: This is intended to be a self-service tool. You are free to modify it in any way to meet your needs. 56 | -------------------------------------------------------------------------------- /mobile-connected-client/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TableauOAuth 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /mobile-connected-client/src/ios/TableauOAuth.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableauOAuth.h 3 | // 4 | 5 | #ifndef TableauOAuth_h 6 | #define TableauOAuth_h 7 | 8 | #import 9 | 10 | @interface TableauOAuth : NSObject 11 | 12 | /** 13 | Load cookies from keychain into cookie storage, where out WebViews can use them. 14 | */ 15 | - (void)loadCookiesFromStorageForServer: (nonnull NSURL *) serverUrl; 16 | 17 | /** 18 | If we have a session cookie, check to see if we're signed in. 19 | If so, request OAuth tokens. 20 | @param serverUrl 21 | The name of the server. 22 | */ 23 | - (void)requestTokensForSession: (nonnull NSURL *) serverUrl; 24 | 25 | /** 26 | Send request to Tableau Server to get initial OAuth access and request token. 27 | This method should only be called after a successful sign-in to ensure that the 28 | wg-session cookie is still valid. 29 | @param serverUrl 30 | The name of the server. 31 | */ 32 | - (void)requestOAuthTokens: (nonnull NSURL *) serverUrl; 33 | 34 | /** 35 | Send request to server to check sign in status. 36 | @param serverUrl 37 | The name of the server. Should include http:// or https:// 38 | ex: http://tableau.example.com 39 | @param siteName 40 | The name of the site that is displayed in the site url. 41 | @param successCallbackBlock 42 | Block containing code to execute if user is signed in. 43 | @param failureCallbackBlock 44 | Block containing code to execute if user is NOT signed in. 45 | */ 46 | - (void)checkSignInStatus: (nonnull NSURL *) serverUrl forSite:(nonnull NSString *) siteName successCallback:(nullable void (^)(void))successCallbackBlock failureCallback:(nullable void (^)(void))failureCallbackBlock; 47 | 48 | /** 49 | Sign out by deleting cookies and OAuth tokens if they exist. 50 | @param serverUrl 51 | The name of the server. 52 | */ 53 | - (void)signOut: (nonnull NSURL *) serverUrl; 54 | 55 | @end 56 | 57 | #endif /* TableauOAuth_h */ 58 | -------------------------------------------------------------------------------- /mobile-connected-client/src/ios/TableauOAuthCordova.h: -------------------------------------------------------------------------------- 1 | /* 2 | TableauOAuthCordova 3 | A Cordova plugin that handles authentication related tasks for the Tableau custom app. 4 | Communicates with JavaScript interface following Cordova Plugin Development guidelines. 5 | For more information: https://cordova.apache.org/docs/en/latest/guide/hybrid/plugins/ 6 | */ 7 | 8 | #ifndef TableauOAuthCordova_h 9 | #define TableauOAuthCordova_h 10 | 11 | #import "TableauOAuth.h" 12 | #import 13 | 14 | @interface TableauOAuthCordova : CDVPlugin 15 | 16 | // CORDOVA SPECIFIC: Handle communication between plugin and JavaScript 17 | - (void)requestOAuthTokensCordova:(nonnull CDVInvokedUrlCommand*)command; 18 | - (void)checkSignInStatusCordova:(nonnull CDVInvokedUrlCommand*)command; 19 | - (void)signOutCordova:(nonnull CDVInvokedUrlCommand*)command; 20 | 21 | @end 22 | 23 | #endif /* TableauOAuthCordova_h */ 24 | -------------------------------------------------------------------------------- /mobile-connected-client/src/ios/TableauOAuthCordova.m: -------------------------------------------------------------------------------- 1 | #import "TableauOAuthCordova.h" 2 | 3 | @implementation TableauOAuthCordova 4 | 5 | TableauOAuth *_auth; 6 | 7 | /** 8 | CORDOVA SPECIFIC: After initial sign in, request OAuth tokens from the server. 9 | */ 10 | - (void)requestOAuthTokensCordova:(nonnull CDVInvokedUrlCommand*)command 11 | { 12 | if(command.arguments == nil) { 13 | // Check that the JavaScript interface did pass some arguments. 14 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No arguments passed from JavaScript interface."]; 15 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 16 | } else if ([command.arguments count] != 1) { 17 | // Check for the proper number of arguments from the JavaScript interface. 18 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Incorrect number of arguments."]; 19 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 20 | } else { 21 | // Get argument information from JavaScript interface. 22 | // If string passed is not a valid url, serverUrl will be initialized as nil. 23 | NSURL *serverUrl = [NSURL URLWithString:[command.arguments objectAtIndex:0]]; 24 | if(_auth == nil) { 25 | _auth = [[TableauOAuth alloc] init]; 26 | } 27 | [_auth requestOAuthTokens:serverUrl]; 28 | } 29 | } 30 | 31 | /** 32 | CORDOVA SPECIFIC: Check whether or not the user is signed in. 33 | */ 34 | - (void)checkSignInStatusCordova:(nonnull CDVInvokedUrlCommand*)command 35 | { 36 | if(command.arguments == nil) { 37 | // Check that the JavaScript interface did pass some arguments. 38 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No arguments passed from JavaScript interface."]; 39 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 40 | } else if ([command.arguments count] != 2) { 41 | // Check for the proper number of arguments from the JavaScript interface. 42 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Incorrect number of arguments."]; 43 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 44 | } else { 45 | // Get argument information from JavaScript interface. 46 | NSURL *serverUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@", [command.arguments objectAtIndex:0]]]; 47 | NSString *siteName = [NSString stringWithFormat:@"%@", [command.arguments objectAtIndex:1]]; 48 | 49 | if(_auth == nil) { 50 | _auth = [[TableauOAuth alloc] init]; 51 | } 52 | [_auth checkSignInStatus:serverUrl forSite:siteName 53 | successCallback:^{ 54 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 55 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 56 | } 57 | failureCallback:^{ 58 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"User is not signed in"]; 59 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 60 | } 61 | ]; 62 | } 63 | } 64 | 65 | /** 66 | CORDOVA SPECIFIC: Sign out the user. 67 | */ 68 | - (void)signOutCordova:(nonnull CDVInvokedUrlCommand*)command 69 | { 70 | if(command.arguments == nil) { 71 | // Check that the JavaScript interface did pass some arguments. 72 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No arguments passed from JavaScript interface."]; 73 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 74 | } else if ([command.arguments count] != 1) { 75 | // Check for the proper number of arguments from the JavaScript interface. 76 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Incorrect number of arguments."]; 77 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 78 | } else { 79 | // Get argument information from JavaScript interface. 80 | NSURL *serverUrl = [NSURL URLWithString:[command.arguments objectAtIndex:0]]; 81 | if(_auth == nil) { 82 | _auth = [[TableauOAuth alloc] init]; 83 | } 84 | [_auth signOut:serverUrl]; 85 | } 86 | } 87 | 88 | @end -------------------------------------------------------------------------------- /mobile-connected-client/www/TableauOAuth.js: -------------------------------------------------------------------------------- 1 | var exec = require('cordova/exec'); 2 | 3 | module.exports = { 4 | requestOAuthTokens: function (serverUrl, success, error) { 5 | exec(success, error, "TableauOAuth", "requestOAuthTokensCordova", [serverUrl]); 6 | }, 7 | checkSignInStatus: function(serverUrl, siteName, success, error) { 8 | exec(success, error, "TableauOAuth", "checkSignInStatusCordova", [serverUrl, siteName]); 9 | }, 10 | signOut: function(serverUrl, error) { 11 | exec(null, error, "TableauOAuth", "signOutCordova", [serverUrl]); 12 | } 13 | }; -------------------------------------------------------------------------------- /samples/BarcodeScanner/Barcode Scanner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Barcode Scanner.xcodeproj/project.xcworkspace/xcshareddata/Barcode Scanner.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "8CA22A273EE8D57AEFE82B71B69A3C326ED14542", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "661D8C2D4993187619AB991BB9BF051AE8AE6EFD" : 9223372036854775807, 8 | "8CA22A273EE8D57AEFE82B71B69A3C326ED14542" : 9223372036854775807 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "C87B7EFB-1AD6-44FC-B360-F094E8D207BE", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "661D8C2D4993187619AB991BB9BF051AE8AE6EFD" : "mobile-app-bootstrap-objc\/samples\/BarcodeScanner\/MTBBarcodeScanner\/", 13 | "8CA22A273EE8D57AEFE82B71B69A3C326ED14542" : "mobile-app-bootstrap-objc\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Barcode Scanner", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "samples\/BarcodeScanner\/Barcode Scanner.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/mikebuss\/MTBBarcodeScanner.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "661D8C2D4993187619AB991BB9BF051AE8AE6EFD" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/tableau\/mobile-app-bootstrap-objc.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8CA22A273EE8D57AEFE82B71B69A3C326ED14542" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /samples/BarcodeScanner/Barcode Scanner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Barcode Scanner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/20/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/20/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TokenManager.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | 22 | // Check keychain and inflate any cookies we've got stored from a previous session. 23 | // Note that this doesn't make much sense for Tableau Public, but does when connecting to your own server. 24 | [TokenManager loadCookiesFromStorageForServer:[NSURL URLWithString:@"https://public.tableau.com"]]; 25 | 26 | return YES; 27 | } 28 | 29 | 30 | - (void)applicationWillResignActive:(UIApplication *)application { 31 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 32 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 33 | } 34 | 35 | 36 | - (void)applicationDidEnterBackground:(UIApplication *)application { 37 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 39 | } 40 | 41 | 42 | - (void)applicationWillEnterForeground:(UIApplication *)application { 43 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 44 | } 45 | 46 | 47 | - (void)applicationDidBecomeActive:(UIApplication *)application { 48 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 49 | } 50 | 51 | 52 | - (void)applicationWillTerminate:(UIApplication *)application { 53 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-small.png", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-small@2x.png", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "size" : "29x29", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-small@3x.png", 29 | "scale" : "3x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@2x.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "size" : "40x40", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-40@3x.png", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "size" : "57x57", 45 | "idiom" : "iphone", 46 | "filename" : "Icon.png", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "size" : "57x57", 51 | "idiom" : "iphone", 52 | "filename" : "Icon@2x.png", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "size" : "60x60", 57 | "idiom" : "iphone", 58 | "filename" : "Icon-60@2x.png", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "size" : "60x60", 63 | "idiom" : "iphone", 64 | "filename" : "Icon-60@3x.png", 65 | "scale" : "3x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "20x20", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "20x20", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "idiom" : "ipad", 79 | "size" : "29x29", 80 | "scale" : "1x" 81 | }, 82 | { 83 | "idiom" : "ipad", 84 | "size" : "29x29", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-40.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-40@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "50x50", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-50.png", 103 | "scale" : "1x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-50@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "72x72", 113 | "idiom" : "ipad", 114 | "filename" : "Icon-72.png", 115 | "scale" : "1x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "Icon-72@2x.png", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "size" : "76x76", 125 | "idiom" : "ipad", 126 | "filename" : "Icon-76.png", 127 | "scale" : "1x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "Icon-76@2x.png", 133 | "scale" : "2x" 134 | }, 135 | { 136 | "size" : "83.5x83.5", 137 | "idiom" : "ipad", 138 | "filename" : "Icon-83.5@2x.png", 139 | "scale" : "2x" 140 | } 141 | ], 142 | "info" : { 143 | "version" : 1, 144 | "author" : "xcode" 145 | } 146 | } -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-50@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon-small@3x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "camera.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "camera@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "camera@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Camera.imageset/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Camera.imageset/camera.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Camera.imageset/camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Camera.imageset/camera@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Camera.imageset/camera@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Camera.imageset/camera@3x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Gears.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Gears@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/LaunchGears.imageset/Gears@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ios7-pulse_TabBar.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ios7-pulse_TabBar@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ios7-pulse_TabBar@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@2x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/Mobile App Bootstrap/Assets.xcassets/Pulse.imageset/ios7-pulse_TabBar@3x.png -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Barcode Scanner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/HomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HomeViewController : UIViewController 12 | 13 | @property (strong, nonatomic) IBOutlet UIWebView *webView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/HomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | #import "MTBBarcodeScanner.h" 11 | #import "Viz1ViewController.h" 12 | 13 | @interface HomeViewController () 14 | 15 | @property (nonatomic, weak) IBOutlet UIView *previewView; 16 | @property (nonatomic, weak) IBOutlet UILabel *cameraUnavailableLabel; 17 | 18 | @property (nonatomic, strong) MTBBarcodeScanner *scanner; 19 | 20 | 21 | @end 22 | 23 | @implementation HomeViewController 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | } 28 | 29 | - (void)viewWillAppear:(BOOL)animated { 30 | 31 | // Check to see if we've got permission to access the camera. 32 | // If we haven't asked before, a dialog will be shown. It will include text set in Info.plist for the Camera Usage Description. 33 | [MTBBarcodeScanner requestCameraPermissionWithSuccess:^(BOOL success) { 34 | if (success) { 35 | // We have permission, so start scanning. 36 | self.cameraUnavailableLabel.hidden = true; 37 | [self startScanning]; 38 | } else { 39 | // This is a little bit belt-and-suspenders: show an alert and show some text over the scanning view. 40 | self.cameraUnavailableLabel.hidden = false; 41 | [self displayPermissionMissingAlert]; 42 | } 43 | }]; 44 | 45 | 46 | } 47 | 48 | - (void)viewWillDisappear:(BOOL)animated { 49 | [self stopScanning]; 50 | [super viewWillDisappear:animated]; 51 | } 52 | 53 | #pragma mark - Scanner 54 | 55 | - (MTBBarcodeScanner *)scanner { 56 | if (!_scanner) { 57 | _scanner = [[MTBBarcodeScanner alloc] initWithPreviewView:_previewView]; 58 | } 59 | return _scanner; 60 | } 61 | 62 | #pragma mark - Scanning 63 | 64 | - (void)startScanning { 65 | 66 | NSError *error = nil; 67 | [self.scanner startScanningWithResultBlock:^(NSArray *codes) { 68 | for (AVMetadataMachineReadableCodeObject *code in codes) { 69 | if (code.stringValue) { 70 | NSLog(@"startScanning: found code: %@", code.stringValue); 71 | 72 | NSString *validBarcode = [self parseValidBarCodeFromScannedString:code.stringValue]; 73 | 74 | if ([validBarcode compare:code.stringValue] == NSOrderedSame) { 75 | 76 | if ([self.parentViewController isKindOfClass:[UITabBarController class]]) { 77 | UITabBarController *parent = (UITabBarController *)self.parentViewController; 78 | 79 | // Just sanity-check this. 80 | if (parent.viewControllers.count < 2) { 81 | NSLog(@"startScanning: our parent only has 1 VC? Bailing out!"); 82 | return; 83 | } 84 | 85 | UIViewController *partnerVC = [parent.viewControllers objectAtIndex:1]; 86 | if (partnerVC && [partnerVC isKindOfClass:[Viz1ViewController class]]) { 87 | // We've got the right VC, now tell it to load the viz and pass in the barcode. 88 | Viz1ViewController *webViewVC = (Viz1ViewController *)partnerVC; 89 | [webViewVC loadVizForBarcode:validBarcode]; 90 | 91 | parent.selectedIndex = 1; 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } error:&error]; 98 | 99 | if (error) { 100 | NSLog(@"An error occurred: %@", error.localizedDescription); 101 | } 102 | } 103 | 104 | - (void)stopScanning { 105 | [self.scanner stopScanning]; 106 | } 107 | 108 | #pragma mark - Helper Methods 109 | 110 | - (void)displayPermissionMissingAlert { 111 | NSString *message = nil; 112 | if ([MTBBarcodeScanner scanningIsProhibited]) { 113 | message = @"This app does not have permission to use the camera."; 114 | } else if (![MTBBarcodeScanner cameraIsPresent]) { 115 | message = @"This device does not have a camera."; 116 | } else { 117 | message = @"An unknown error occurred."; 118 | } 119 | 120 | [[[UIAlertView alloc] initWithTitle:@"Scanning Unavailable" 121 | message:message 122 | delegate:nil 123 | cancelButtonTitle:@"Ok" 124 | otherButtonTitles:nil] show]; 125 | } 126 | 127 | - (NSString *)parseValidBarCodeFromScannedString:(NSString *)scannedString { 128 | // The scanner retrieved a string, but it could have anything inside...some other barcode we don't understand, a URL, javascript, etc. 129 | // So here we're trying to grab a barcode in a format that we expect. For valid codes friendly to us, this should just return the same thing the scanner saw. 130 | 131 | // Our sample barcodes are all integers. 132 | int scannedInt = [scannedString intValue]; 133 | 134 | return [NSString stringWithFormat:@"%d",scannedInt]; 135 | } 136 | 137 | @end 138 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | NSCameraUsageDescription 29 | For scanning barcodes 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIStatusBarTintParameters 39 | 40 | UINavigationBar 41 | 42 | Style 43 | UIBarStyleDefault 44 | Translucent 45 | 46 | 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/TokenManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TokenManager.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface TokenManager : NSObject 13 | 14 | + (void)loadCookiesFromStorageForServer: (nonnull NSURL *) serverUrl ; 15 | + (void)createTokensIfNeeded: (nonnull NSURL *) serverUrl; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/TokenManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TokenManager.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "TokenManager.h" 10 | #import "TableauOAuth.h" 11 | 12 | @implementation TokenManager 13 | 14 | 15 | static TableauOAuth *_auth; 16 | 17 | + (void)initialize { 18 | if (self == [TokenManager class]) { 19 | _auth = [[TableauOAuth alloc] init]; 20 | } 21 | } 22 | 23 | + (void)loadCookiesFromStorageForServer: (nonnull NSURL *) serverUrl { 24 | [_auth loadCookiesFromStorageForServer: serverUrl]; 25 | } 26 | 27 | + (void)createTokensIfNeeded: (nonnull NSURL *) serverUrl { 28 | [_auth requestTokensForSession:serverUrl]; 29 | } 30 | 31 | 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Viz1ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // Viz1ViewController.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface Viz1ViewController : UIViewController 13 | 14 | @property (strong, nonatomic) IBOutlet WKWebView *webView; 15 | 16 | - (void)loadVizForBarcode:(NSString *)scannedString; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/Viz1ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // Viz1ViewController.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "Viz1ViewController.h" 10 | #import "WebViewDelegate.h" 11 | 12 | 13 | @implementation Viz1ViewController 14 | WebViewDelegate *webViewDelegate; 15 | 16 | // The URL pointing to our viz. 17 | // The field with the barcode is named "MRN (Scanning Field)" in our viz, and we want to filter based on this field. 18 | // So we send field=value as a query parameter. 19 | // Note the %20, %28, and %29 in place of spaces and parentheses in the field name, as the field name has to be urlencoded. 20 | NSString *baseURLString = @"https://public.tableau.com/views/Hackathon_15711602536340/Dashboard1?:embed=y&:tooltip=n&:toolbar=n&:showVizHome=no&MRN%20%28Scanning%20Field%29="; 21 | 22 | // Save this in case we're asked to load a barcode before we're ready. 23 | NSString *barCodeToLoad; 24 | 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | 29 | // Set up our WebViewDelegate. 30 | webViewDelegate = [[WebViewDelegate alloc] init]; 31 | self.webView.navigationDelegate = webViewDelegate; 32 | 33 | // If we've got a barcode from earlier, load it here. 34 | if (barCodeToLoad != nil) { 35 | [self loadBarcodeInWebView: barCodeToLoad]; 36 | barCodeToLoad = nil; 37 | } 38 | } 39 | 40 | - (void)loadVizForBarcode:(NSString *)scannedString { 41 | // Our viz field is expecting all digits, and our barcodes are all digits. 42 | // But if the user scans a different kind of barcode it could pass in characters like '&' which would need to be URL-encoded. 43 | // You'll also want to sanity-check the barcode string to ensure it meets your requirements (length, content, etc.), either here or in HomeViewController. 44 | 45 | NSString *escapedString = [scannedString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]; 46 | 47 | if (self.webView == nil) { 48 | // We haven't made the WebView yet, so save the barcode to load after we've started things up in viewDidLoad. 49 | barCodeToLoad = escapedString; 50 | } else { 51 | [self loadBarcodeInWebView: escapedString]; 52 | } 53 | } 54 | 55 | - (void)loadBarcodeInWebView:(NSString *)barcode { 56 | 57 | // Append the barcode to the end of the URL. 58 | NSString *vizWithFilterURLString = [baseURLString stringByAppendingString:barcode]; 59 | 60 | // Create the URL, tell our webview to load it. 61 | NSURL *url = [NSURL URLWithString:vizWithFilterURLString]; 62 | [self.webView loadRequest:[NSURLRequest requestWithURL: url]]; 63 | } 64 | 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/WebViewDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewDelegate.h 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface WebViewDelegate : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/WebViewDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewDelegate.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/23/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import "WebViewDelegate.h" 10 | #import "TokenManager.h" 11 | 12 | @implementation WebViewDelegate 13 | 14 | # pragma mark WKNavigationDelegate 15 | 16 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation; { 17 | if ((webView != nil) && (webView.URL != nil)) { 18 | 19 | // Create tokens if needed; this will check our cookies before requesting. 20 | [TokenManager createTokensIfNeeded:webView.URL]; 21 | } 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/Mobile App Bootstrap/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Mobile App Bootstrap 4 | // 5 | // Created by Ron Theis on 10/20/16. 6 | // Copyright © 2016 Tableau. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/README.md: -------------------------------------------------------------------------------- 1 | # Mobile App Bootstrap sample: Barcode Scanner 2 | 3 | A sample app to demonstrate using the camera to scan a barcode and filter a viz based on the barcode. 4 | 5 | ## Installation 6 | 7 | Hopefully straightforward: 8 | 9 | 1. This sample should have been downloaded as part of the entire Mobile App Bootstrap [repository](https://github.com/tableau/mobile-app-bootstrap), as this sample refers to some files in higher-level directories. 10 | 11 | Note: Older versions of git might need `git submodule update --init --recursive` run from this directory. If this Barcode Scanner directory has a populated `MTBBarcodeScanner` directory, then running the extra git command shouldn't be necessary. 12 | 13 | ## Starting the Tableau Sample App 14 | 15 | 1. Open `Mobile App Bootstrap.xcodeproj` in Xcode 16 | 2. Choose/click "Run" 17 | 18 | This will literally get the app running, but if it's run in a (camera-less) simulator, a dialog will appear about missing a camera. 19 | 20 | For added flavor, try: 21 | 1. Print out `sample-barcodes.png` on paper. 22 | 2. In Xcode, choose to run the app on a physical phone/tablet with a camera. 23 | 3. On-device, permit access to the camera via the dialog. 24 | 4. Point the now-scanning camera in the app at one of the barcodes on paper. 25 | 5. Once the scanner detects a barcode, the viz should load, filtered to that barcode. 26 | 27 | # Project Layout 28 | 29 | `HomeViewController` contains the scanning code, using the delightful [MTBBarcodeScanner](https://github.com/mikebuss/MTBBarcodeScanner) library to interface with the built-in iOS barcode scanning functionality. 30 | 31 | `Viz1ViewController` just shows the viz, with a field filtered to the scanned barcode value. 32 | 33 | ## Customization Points 34 | 35 | `AppDelegate.m` 36 | Change `https://public.tableau.com` to your internal server. 37 | 38 | `Viz1ViewController.m` 39 | Change the viz URL to point to your own viz. Be sure to change the URL query parameters to match your field containing barcode values. Note: The viz URL currently points to a viz that no longer exists on Tableau Public; sorry about that, we'll fix it soon. 40 | 41 | `HomeViewController.m` 42 | The method `parseValidBarCodeFromScannedString` contains some logic to sanity-check the barcode. This check could take place here or in `Viz1ViewController`. The string returned from the barcode scanner could be literally anything (letters, numbers, symbols, emojis, URLs, javascript, etc.), so it needs to be sanitized. 43 | 44 | [MTBBarcodeScanner](https://github.com/mikebuss/MTBBarcodeScanner) itself has some pretty interesting sample code, demonstrating things like drawing a rectangle around a found barcode, turning on the camera flash, etc. 45 | 46 | # Future Plans 47 | 48 | * None at this point. 49 | 50 | * Anything else you'd like to see? Please get in touch with us through the [Tableau Developer Community](developer.tableau.com)! 51 | 52 | 53 | # Support 54 | 55 | This sample is not officially 'blessed' by Tableau Engineering or Support. What does that mean? We didn't have a QA team test it. It's a tool for learning how to embed vizzes inside a mobile application. You should not expect that there are 0 bugs. 56 | 57 | If you have problems getting it to work, feel free to email us with questions, but we can't promise quick responses. The [Tableau Developer Community](developer.tableau.com) is also a great resource if you need help. 58 | 59 | A standard disclaimer: mobile-app-bootstrap is made available AS-IS with no support and no warranty whatsoever. Despite efforts to write good and useful code there may be bugs that cause unexpected and undesirable behavior. The software is strictly “use at your own risk.” 60 | 61 | The good news: This is intended to be a self-service tool. You are free to modify it in any way to meet your needs. 62 | 63 | 64 | -------------------------------------------------------------------------------- /samples/BarcodeScanner/sample_barcodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/mobile-app-bootstrap-objc/5ee89aef7968c9618e7d5f39539cfd177cbe76d6/samples/BarcodeScanner/sample_barcodes.png -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # Mobile App Bootstrap: Samples Directory 2 | 3 | Sample code directory for the Mobile App Bootstrap. Each sample/directory tries to demonstrate one particular feature in a self-contained way. 4 | 5 | BarcodeScanner: Uses the camera to scan a barcode to then filter a viz. 6 | --------------------------------------------------------------------------------