2015年11月25日

Ionic Framework 教學 - 11. App 上架

Android 和 iOS 分開上架,上架前要先修改參數,以 Android 為例,我們先移除 console log (注意如果你之後要繼續開發的話要把這個 plugin 加回來)。
cordova plugin rm cordova-plugin-console

然後可以修改 mysidemenu/platforms/android/AndroidManifest.xml 裡面的 android:versionName="0.0.1"
之後跑 release build 然後產生 my-release-key.keystore 來簽署我們的 apk ,再用 zipalign 打包好,就可以到 Google Play 上架了
cordova build --release android
keytool
cd platforms/android/build/outputs/apk/
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
ls
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore android-release-unsigned.apk alias_name
~/android/android-sdk-macosx/build-tools/22.0.1/zipalign -v 4 android-release-unsigned.apk myapp.apk

上架填寫表單的時候要注意不要使用網路上抓來的圖片。











Ionic Framework 教學 - 10. 使用 Cordova Plugin




上一章

我們來試著製作一個會用到相機的 App 來練習 Plugin 的使用,相機的功能是藉由 Cordova Plugin 來連接的。


首先 Shell 指令進入我們的 project,然後增加手機目標平台系統,ios 或是 android
cd mysidemenu
ionic platform add android
ionic platform add ios

然後安裝 Camera Plugin 和 Canvas2Image Plugin
npm update -g cordova
cordova platform update android@5.0.0
cordova plugin add cordova-plugin-camera
cordova plugin add https://github.com/devgeeks/Canvas2ImagePlugin.git
cordova plugin add cordova-plugin-whitelist

在 www/templates/menu.html 增加一個選單
<ion-item menu-close href="#/app/photo">
  Wanted
</ion-item>

然後增加 www/templates/photo.html
<ion-view>
  
  <!-- add a camera button to take a photo -->
  <ion-nav-buttons side="secondary">
    <button
      ng-click="capturePhoto()"
      class="button button-icon icon icon-small ion-camera">
    </button>
  </ion-nav-buttons>

  <ion-content>

    <div
      style="
        width:320px;
        height:453px;
        overflow: hidden;
        margin: 0 auto;
      ">
      <div
        style="
          width:320px;
          height: 1000px;
          background-image:url('{{photo.url}}');
          background-repeat: no-repeat;
          background-size: contain;
        ">
        <img ng-src="{{framePhoto}}"
          style="
            width:320px;
          ">
      </div>
    </div>
    <button class="button button-full" ng-click="save()">Save</button>
  </ion-content>

</ion-view>

這裡可以看到我們使用一個 background image ,圖片位置是一個變數 photo.url ,然後前面放一個圖片 img ,還有兩個按鈕對影兩個函式, capturePhoto 用來拍照, save() 用來儲存照片。
接下來我們定義一個 Controller 來處理這些邏輯。
.controller('PhotoCtrl', function($scope) {
  
  $scope.framePhoto = 'http://i.imgur.com/5WkxqFC.png?1';
  //photo to show in view page
  $scope.photo = {};
  
  //options to take a photo
  var photoOptions = {};
  if( typeof Camera !== 'undefined'){

    //https://github.com/apache/cordova-plugin-camera
    photoOptions = {
      quality: 100,
      destinationType: Camera.DestinationType.FILE_URL,
      sourceType: Camera.PictureSourceType.CAMERA, //[PHOTOLIBRARY, CAMERA, SAVEDPHOTOALBUM]
      encodingType: 1, //jpeg:0 png:1
      targetWidth: 320,
      targetHeight: 453
    };

  }

  /**
   * Capture a photo, when user click the camera icon
   */
  $scope.capturePhoto = function() {
    if(navigator.camera){
      navigator.camera.getPicture(
        onGetPictureSuccess, onGetPictureError, photoOptions);
    }
  };

  /**
   * Called when the photo is taken
   * @param  {string} result photo local url
   */
  function onGetPictureSuccess (result) {
    console.log('onGetPictureSuccess', result);
    $scope.photo.url = result;
    $scope.$apply();
  }

  /**
   * Called when the capture photo runs into an error
   * @param  {object} err [description]
   */
  function onGetPictureError (err) {
    console.error('error', err);
  }



  /**
   * Load an image from a file path, call 'onload' function when loaded.
   * @param  {string} src    image file path
   * @param  {function} onload callback function
   * @return {image}        
   */
  function loadImage(src, onload) {
    console.log('loadImage');
    var img = new Image();

    img.onload = onload;
    img.src = src;
    return img;
  }

  
  /**
   * Combines two images in a canvas, use the size of image 1.
   * @param  {string}   src1     file path of image 1
   * @param  {string}   src2     file path of image 2
   * @param  {Function} callback function when 2 images combined
   */
  function combineImages (src1, src2, callback) {
    console.log('combineImages', [src1, src2]);
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d'), dataURL;
    var img1 = loadImage(src1, onload);
    var img2 = loadImage(src2, onload);
    var i = 0, base64Photo;
    function onload() {
      i += 1;
      if(i == 2){ //when 2 images are both loaded
        i = 0;
        canvas.width = img1.width;
        canvas.height = img1.height;
        //draw img2 according to img1's ratio
        ctx.drawImage(img2, 0, 0, img2.width, img2.height, 0, 0, img1.width, img1.width*img2.height/img2.width);
        ctx.drawImage(img1, 0, 0);
        callback(canvas);
      }
    }
  }

  /**
   * save image from canvas to gallery
   * @param  {canvas} canvas
   */
  function saveImageFromCanvas(canvas){
    window.canvas2ImagePlugin.saveImageDataToLibrary(
      function(msg){
          console.log('canvas2ImagePlugin', msg);
      },
      function(err){
          console.log('canvas2ImagePlugin', err);
      },
      canvas
    );
  }

  $scope.save = function() {
    //combine 2 images 
    combineImages($scope.framePhoto, $scope.photo.url, saveImageFromCanvas);
  };

})

理解程式內容請一邊閱讀程式註解。 最後我們在 www/js/app.js 增加一組 routes
.state('app.photo', {
  url: '/photo',
  views: {
    'menuContent': {
      templateUrl: 'templates/photo.html',
      controller: 'PhotoCtrl'
    }
  }
})

試著執行這個頁面,發現不能執行,因為電腦上面沒有這些 Plugin 對應的手機程式,我們要到手機上去測試執行。
有別於之前上傳的測試方式,這次我們用到 Plugin 裡面因為包含原生的程式,我們必須要直接編譯這個 App。
請到 App Store 安裝 Xcode 或是到 Android 官網下載 Android SDK,然後安裝好並且把 Android SDK 環境變數加到系統內。
接著到命令列 Shell 編譯你需要的對應的手機系統。
ionic build ios
ionic build android

編譯沒有問題之後,如果你是使用 Android 系統,你可以接上你的手機測試,請打開你手機的 developer mode ,ios 的使用者可以跑 simulator。
ionic run ios
ionic run android

遺憾的是,ios simulator 只能檢查你編譯成功,沒有支援相機功能。
iOS 的話我們進到 Xcode 打開 mysidemenu project , File > Open... > mysidemenu/platforms/ios/mysidemenu.xcodeproj
連接手機平板裝置後開始編譯。











2015年11月19日

Ionic Framework 教學 - 9. 簡易聊天室設定

第八章

我們來做一個設定頁面,讓我們在聊天室講話的時候可以有一個暱稱。

請大家建立一個設定頁面 www/templates/settings.html

<ion-view view-title="設定">
  <ion-content>
    <ion-list>

      <ion-item class="item item-input-inset">
        <label class="item-input-wrapper">
          <input type="text" placeholder="Nickname" ng-model="currentUser.displayName">
        </label>
        <button class="button button-small" ng-click="saveName()">
          Save
        </button>
      </ion-item>

      <label class="item item-input item-select">
        <div class="input-label">
          Color
        </div>
        <select class="" 
          ng-model="settings.color" 
          ng-options="c.value as c.name for c in colors" 
          ng-change="showColor()">
      </label>

    </ion-list>
  </ion-content>
</ion-view>

這裡使用了 currentUser.displayName 來當做顯示名稱,saveName()函式來儲存名稱,使用 select 來選擇顏色,當顏色被選擇之後會觸發 ng-change ,就可以用 showColor()來看看顏色變數被改變了嗎。
然後我們來定義這些功能,請增加一個 SettingsCtrl

.controller('SettingsCtrl', function($scope, $rootScope) {

  //gloabl variable, local storage
  $scope.currentUser = JSON.parse(window.localStorage.currentUser) || {};

  //global variable, temporary
  $rootScope.settings = $rootScope.settings || {};

  //https://en.wikipedia.org/wiki/Web_colors
  $scope.colors = [
    {
      name: 'Navy',
      value: '#000080',
    },
    {
      name: 'Crimson',
      value: '#DC143C'
    },
    {
      name: 'DimGray',
      value: '#696969'
    }
  ];

  $scope.showColor = function() {
    console.log($rootScope.settings);
  };

  $scope.saveName = function() {
    alert($scope.currentUser.displayName + ' saved!');
    window.localStorage.currentUser = JSON.stringify($scope.currentUser);
  };

})

這裡我們有兩種全域變數的寫法,一種是存在 local storage 一種是存在 $rootScope 裡面,等下我們來看看這兩種用法的差別。
注意這裡,在 window.localStorage 中必須要用字串的形式儲存,所以我們用 JSON.stringify 來把物件轉換成字串。讀取的時候則用 JSON.parse 來把字串轉換成物件。
然後我們在 www/js/app.js 增加一個 route state 來處理 settings

.state('app.settings', {
  url: '/settings',
  views: {
    'menuContent': {
      templateUrl: 'templates/settings.html',
      controller: 'SettingsCtrl'
    }
  }
})
然後我們在 www/templates/messages.html 增加一個按鈕可以連到設定頁面,把這個按鈕增加到 ion-view 的裡面,ion-content的上面。

  <ion-nav-buttons side="secondary">
    <button ui-sref="app.settings"
      class="button button-icon icon ion-gear-a">
    </button>
  </ion-nav-buttons>

這是一個 header button , secondary 代表他在次要位置。
然後我們修改 www/templates/messages.html 的內容,增加了顯示名稱和顏色,完成後大概成為這樣。
<ion-view view-title="{{chatroom.name}}">
  <ion-nav-buttons side="secondary">
    <button ui-sref="app.settings"
      class="button button-icon icon ion-gear-a">
    </button>
  </ion-nav-buttons>
  <ion-content>
    <ion-list>
      <ion-item class="item-input-inset">
        <label class="item-input-wrapper">
          <input type="text" placeholder="New Message" ng-model="newMessage.content">
        </label>
        <button class="button button-small" ng-click="sendMessage()">
          Send
        </button>
      </ion-item>
      <ion-item class="item-text-wrap" ng-repeat="message in messages.slice().reverse()">
        <strong class="" ng-show="message.displayName">{{message.displayName}}:</strong>
        <span style="color:{{message.color || '#000'}}">
          {{message.content}}
        </span>
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

然後我們回頭修改 MessagesCtrl 來讓送出的 message 夾帶 displayName 和 color 的資訊。
.controller('MessagesCtrl', function($scope, $state, $rootScope, ChatroomService, MessageService) {

  console.log('Chatroom Id: ', $state.params.roomId);

  //get current chatroom from roomId
  $scope.chatroom = ChatroomService.get($state.params.roomId) || {};
  console.log('This is chatroom: ', $scope.chatroom.name);

  //get messages in this chatroom
  $scope.messages = MessageService.all($state.params.roomId);

  //global variable, localStorage
  var currentUser = JSON.parse(window.localStorage.currentUser) || {};
  $rootScope.settings = $rootScope.settings || {};
  console.log($rootScope.settings);

  $scope.newMessage = {};

  $scope.sendMessage = function () {
    $scope.newMessage.color = $rootScope.settings.color || '#000';
    $scope.newMessage.displayName = currentUser.displayName;
    console.log('New message: ', $scope.newMessage);

    //send message
    MessageService.send($scope.newMessage).then(function() {
      $scope.newMessage.content = '';
    });
  };

})

如果你重新載入程式,你會發現 color 的設定不見了,但是你的暱稱還保留著。

藉由這個練習,我們可以知道 localStorage 和 $rootScope 的身為全域變數的方便強大和危險,強大是在任何地方修改這個變數都有效,危險也是因為你無法確定這個變數會被什麼函式在任何什麼地方修改了,造成不是你預期的效果。

練習題:使用者設定顏色之後,要去聊天室打字才能知道是什麼顏色,這不是一個好的使用者體驗,如何在選擇完顏色的時候讓使用者知道是什麼顏色呢?






Ionic Framework 教學 - 8. 簡易聊天室

第六章第七章

設定好 Firebase 後,我們可以來做簡易聊天室,了解資料怎麼和後台連結,為了方便教學,這裡的聊天室我們只做匿名聊天室。
首先我們要做一個聊天室列表,我們增加一個 ChatroomsCtrl Controller,注意這裡 Chatrooms 是複數。

.controller('ChatroomsCtrl', function($scope) {

  $scope.chatrooms = [];
  
  $scope.newChatroom = {};

  $scope.createChatroom = function () {
    console.log($scope.newChatroom);
  };
})

記得 $scope 代表是 View 要使用的變數,這裡我們定義了 chatrooms 陣列, newChatroom 物件和 createChatroom 函式。
console.log 代表在瀏覽器 console 把變數內容印出來看看的意思。
然後建立 www/templates/chatrooms.html
<ion-view view-title="Chatrooms">
  <ion-content>
    <ion-list>
      <ion-item class="item-input-inset">
        <label class="item-input-wrapper">
          <input type="text" placeholder="New Chatroom Name" ng-model="newChatroom.name">
        </label>
        <button class="button button-small" ng-click="createChatroom()">
          Create
        </button>
      </ion-item>
      <ion-item ng-repeat="chatroom in chatrooms" href="#/app/chatrooms/{{chatroom.$id}}">
        {{chatroom.name}}
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>
其中 ng-model 代表使用變數,ng-repeat 代表使用陣列中的變數。
然後在 www/js/app.js 增加一個 route
  .state('app.chatrooms', {
    url: '/chatrooms',
    views: {
      'menuContent': {
        templateUrl: 'templates/chatrooms.html',
        controller: 'ChatroomsCtrl'
      }
    }
  })

我們順便把 www/js/app.js 最下面的 $urlRouterProvider.otherwise('/app/playlists'); 改掉,這是預設的顯示頁面,如果連到任何未定義的 url 就會改連到這裡,我們改成 /app/chatrooms。
$urlRouterProvider.otherwise('/app/chatrooms');

最後在 www/templates/menu.html 增加一個 Chatrooms 選項

<ion-item menu-close href="#/app/chatrooms">
  Chatrooms
</ion-item>

現在可以按按看按鈕,理解一下程式內容。
然後我們增加一個 www/js/services.js
var firebaseUrl = "https://your-project-id.firebaseio.com/";
angular.module('starter.services', [])
//Firebase datastore reference
.factory("FirebaseRef", function($firebaseAuth) {
  return new Firebase(firebaseUrl);
})
//for log in and sign up
.factory("Auth", function($firebaseAuth) {
  var usersRef = new Firebase(firebaseUrl+"users");
  return $firebaseAuth(usersRef);
})
.factory("ChatroomService", function($firebaseArray) {
  var roomsRef = new Firebase(firebaseUrl+"chatrooms");
  var rooms = $firebaseArray(roomsRef);
  return {
    all: function() {
      console.log(rooms);
      return rooms;
    },
    get: function(roomId) {
      return rooms.$getRecord(roomId);
    },
    create: function(chatroom) {
      console.log('Adding chatroom: ', chatroom.name);
      return rooms.$add(chatroom);
    }
  }
});

其中 your-project-id 請填入你在 Firebase 申請的 Project ID ,這裡我們在做一個和 Firebase 溝通的 Chatroom service 。
這裡我們定義了 ChatroomService 服務,他主要有三個功能 all 來取得所有的聊天室, get 來取得特定 ID 的聊天室, create 來創造一個聊天室。
我們在第六章有說到,在 www/index.html 中,需要引用 services.js 來讓這個服務可以載入到頁面中。
...
<script src="js/app.js"></script>
<script src="js/services.js"></script>
...

在第六章也有提到,在 app.js 最上面要加上 starter app 對 'firebase' 和 'starter.services' 的依賴關係,這樣 angular 才知道要引用這些程式和服務。

angular.module('starter', ['ionic', 'firebase', 'starter.services', 'starter.controllers'])

然後讓我們的 ChatroomsCtrl 來使用這個 service 。


.controller('ChatroomsCtrl', function($scope, ChatroomService) {

  $scope.chatrooms = ChatroomService.all();
  
  $scope.newChatroom = {};

  $scope.createChatroom = function () {
    console.log($scope.newChatroom);
    ChatroomService.create($scope.newChatroom);
  };
})

這樣就可以順利增加 Chatroom 了,如果你到 Firebase project console 也可以看到對應的資料被增加上去了,比如說我增加了三個聊天室,下面圖片亂數的部分就是 Firebase 給這三個聊天室的特殊 ID。











目前Chatroom bar 點下去是沒有用的,因為對應的路徑 #/app/chatrooms/{{chatroom.$id}} 還沒有定義。

現在我們來製作可以發話的聊天室。
首先我們增加這個聊天室訊息的 Service 。
在 www/js/services.js 下面增加 MessageService 。

.factory("MessageService", function($firebaseArray) {
  var messagesRef = new Firebase(firebaseUrl+"messages");
  var messages;
  return {
    all: function(roomId) {
      console.log('get messages from: ', roomId);
      var roomRef = new Firebase(firebaseUrl + "chatrooms/" + roomId + "/messages");
      messages = $firebaseArray(roomRef);
      return messages;
    },
    send: function(message) {
      console.log('Adding message: ', message);
      return messages.$add(message);
    }
  };
})

這裡我們定義了 MessageService 有兩個功能, all 是根據某個 chatroom 的 ID 找出所有的對話訊息的 messages ,send 是用來發送訊息,就是把新的訊息加入 Firebase 中,這個 chatroom 的 messages 陣列。

然後我們就可以在 Controller 中使用這個 Services 了,我們建立一個 MessagesCtrl

.controller('MessagesCtrl', function($scope, $state, ChatroomService, MessageService) {

  console.log('Chatroom Id: ', $state.params.roomId);

  //get current chatroom from roomId
  $scope.chatroom = ChatroomService.get($state.params.roomId) || {};
  console.log('This is chatroom: ', $scope.chatroom.name);

  //get messages in this chatroom
  $scope.messages = MessageService.all($state.params.roomId);

  $scope.newMessage = {};

  $scope.sendMessage = function () {
    console.log('New message: ', $scope.newMessage);

    //send message
    MessageService.send($scope.newMessage);
  };

})

然後我們來建立這個聊天室的訊息頁面,建立 www/templates/messages.html

<ion-view view-title="{{chatroom.name}}">
  <ion-content>
    <ion-list>
      <ion-item class="item-input-inset">
        <label class="item-input-wrapper">
          <input type="text" placeholder="New Message" ng-model="newMessage.content">
        </label>
        <button class="button button-small" ng-click="sendMessage()">
          Send
        </button>
      </ion-item>
      <ion-item class="item-text-wrap" ng-repeat="message in messages.slice().reverse()">
        {{message.content}}
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

這裡可以看到 chatroom 代表當前這個聊天室,newMessage 代表 input 輸入框中新增的訊息, sendMessage 代表按鈕按下去後送出訊息,ng-repeat="message in messages" 代表列出所有在 messages 中的訊息。
然後我們來增加一個 route ,修改 www/js/app.js
.state('app.messages', {
  url: '/chatrooms/:roomId',
  views: {
    'menuContent': {
      templateUrl: 'templates/messages.html',
      controller: 'MessagesCtrl'
    }
  }
})
因為我們可以有很多個聊天室,每個聊天室的 url 不同,所以注意這裡的 :roomId ,代表這個 url 願意接受變數 roomId,並且把變數 roomId 傳給 MessageCtrl Controller 使用,請對照 chatrooms.html 的 href="#/app/chatrooms/{{chatroom.$id}}" 觀察,我們把 url 中的 chatroom.$id 對應到 state route 的 roomId , 使用者進到訊息頁面的時候 route 把 roomId 接受到的值給了新的 controller 中的 $state.params.roomId,我們是用這個介面來達成動態頁面的變數傳遞。

這裡編輯好之後就可以告一段落了。

發送訊息之後,各位也可以到 Firebase console 去看你的資料結構。












下一章,簡易聊天室設定



Ionic Framework 教學 - 7. 簡易登入和登出

繼續上一章的教學,我們來做 Firebase 的使用者登入。
這篇也會提到 LocalStorage 的基本用法。
首先我們建立一個使用者資訊頁面 www/templates/profile.html

<ion-view view-title="使用者資訊">
  <ion-content>
    <div class="list">
      <div class="item">
        Name: {{currentUser.displayName}}
      </div>
    </div>
  </ion-content>
</ion-view>

定義一個 Controller ProfileCtrl

...
.controller('ProfileCtrl', function() {
})
...

然後加入 app.js route
...
.state('app.profile', {
  url: '/profile',
  views: {
    'menuContent': {
      templateUrl: 'templates/profile.html',
      controller: 'ProfileCtrl'
    }
  }
})
...

修改 menu.html 增加 Profile 項目

<ion-item menu-close href="#/app/profile">
  Profile
</ion-item>

連到 http://localhost:8100/#/app/profile 會發現這個頁面目前沒有正確的內容,因為 currentUser 還沒有定義。
現在我們來實作登入功能。
第一步我們直接修改 www/templates/login.html

<ion-modal-view>
  <ion-header-bar>
    <h1 class="title">登入</h1>
    <div class="buttons">
      <button class="button button-clear" ng-click="closeLogin()">關閉</button>
    </div>
  </ion-header-bar>
  <ion-content>
    <form ng-submit="doLogin()">
      <div class="list">
        <label class="item item-input">
          <span class="input-label">Email</span>
          <input type="text" ng-model="loginData.email">
        </label>
        <label class="item item-input">
          <span class="input-label">Password</span>
          <input type="password" ng-model="loginData.password">
        </label>
        <label class="item">
          <button class="button button-block button-positive" type="submit">登入</button>
        </label>
      </div>
    </form>
  </ion-content>
</ion-modal-view>

注意這裡我們改用了 loginData 方便使用上了解。
然後我們在 controllers.js 定義 loginData 和 doLogin()

...
.controller('AppCtrl', function($scope, $ionicModal, $timeout, $rootScope, $state, $ionicLoading, 
  FirebaseRef, Auth) {

  // With the new view caching in Ionic, Controllers are only called
  // when they are recreated or on app start, instead of every page change.
  // To listen for when this page is active (for example, to refresh data),
  // listen for the $ionicView.enter event:
  //$scope.$on('$ionicView.enter', function(e) {
  //});

  // Form data for the login modal
  $scope.loginData = {};

  // Create the login modal that we will use later
  $ionicModal.fromTemplateUrl('templates/login.html', {
    scope: $scope
  }).then(function(modal) {
    $scope.modal = modal;
  });

  // Triggered in the login modal to close it
  $scope.closeLogin = function() {
    $scope.modal.hide();
  };

  // Open the login modal
  $scope.login = function() {
    $scope.modal.show();
  };

  $scope.loginData = {};

  // Perform the login action when the user submits the login form
  $scope.doLogin = function() {
    console.log($scope.loginData);
    var loginData = $scope.loginData;

    if (loginData && loginData.email && loginData.password) {
      $ionicLoading.show({
        template: '登入中...'
      });
      Auth.$authWithPassword({
        email: loginData.email,
        password: loginData.password
      }).then(function (authData) {
        console.log(authData);
        FirebaseRef.child("users").child(authData.uid).once('value', function (snapshot) {
          var val = snapshot.val();
          console.log(val);
          window.localStorage.currentUser = JSON.stringify(val);
          $scope.$apply(function () {
            $rootScope.currentUser = val;
          });
        }, function(err) {
          console.error(err);
        });
        $ionicLoading.hide();
        $scope.closeLogin();
        $state.go('app.profile');
      }).catch(function (error) {
        alert("登入失敗 " + error.message);
        $ionicLoading.hide();
      });
    } else {
      alert("輸入 Email 和 Password")
    }
  };
})
...

這裡可以看到使用者登入後,我們把使用者名稱儲存到 window.localStorage.currentUser 中,這個 window.localStorage 儲存內容必須要使用字串,所以我們把用 JSON.stringify 把使用者資訊從 object 轉換成字串。
window.localStorage 的內容可能被同樣機器上的其他程序看到和使用,所以不建議儲存使用者的秘密資訊,使用者資訊建議儲存在 server 。

我們修改 app.js 的起始執行內容,把 currentUser 的內容代入到 currentUser ,這樣各個 View 都可以使用了。
...
.run(function($ionicPlatform, $rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      cordova.plugins.Keyboard.disableScroll(true);

    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
    
    console.log(JSON.parse(window.localStorage.currentUser));
    $rootScope.currentUser = JSON.parse(window.localStorage.currentUser) || null;

  });
})
...

接下來實作登出功能。
修改 profile.html ,增加登出按鈕。

<ion-view view-title="使用者資訊">
  <ion-content>
    <div class="list">
      <div class="item">
        Name: {{currentUser.displayName}}
      </div>
    </div>
    <button class="button button-full button-positive" ng-click="logout()">登出</button>
  </ion-content>
</ion-view>

然後修改 ProfileCtrl
...
.controller('ProfileCtrl', function($scope, $rootScope, Auth) {
  $scope.logout = function () {
    $rootScope.currentUser = null;
    window.localStorage.currentUser = null;
    Auth.$unauth();
  };
})
...

現在可以試試看登入和登出了。

下一章,簡易聊天室