アプリのデザインリニューアル中。その1

アプリのデザインリニューアルに思ったより時間がかかっているので途中経過を報告

ホーム画面

f:id:wheatandcat:20190705074025p:plain:w250

スケジュール一覧

f:id:wheatandcat:20190705074213p:plain:w250

スケジュール作成

f:id:wheatandcat:20190708005358p:plain:w250

テキストを2回タッチで候補一覧を表示

f:id:wheatandcat:20190708005454p:plain:w250

スケジュール詳細の編集画面

f:id:wheatandcat:20190707191825p:plain:w250

やり出すと、いろいろ凝り出しすもので iOSはスクロールのバウンスの裏も色が変わるように変更

f:id:wheatandcat:20190707192150p:plain:w250

あとキーボードが入力の邪魔になっていたので、フォーカスするとスクロールするように修正

一旦ここまで 今週中くらいで仕上げたいところ。。。

Figma使ってデザインを管理してみる

新しいアイコンができたので、 合わせてアプリのデザインをリニューアルしようと作成中

ちなみに新しいアイコンは、こちら

ということで、合わせてデザインの管理もFigmaに移行しました

Figmaとは

www.figma.com

アプリとブラウザで使用できるデザインツール 元々この手のツールはSketchで管理していることが多かったけど、こちらは無料な上にブラウザでも使用できて、mac以外でも使えてとかなりいい感じのツールになってます。

早速、作っていきます

初期画面は、こんな感じ

f:id:wheatandcat:20190626230328p:plain

Frameは各デバイスサイズに合わせて作成できます。

f:id:wheatandcat:20190626230420p:plain

今回はiPhoneXで作成

まずは、アイコンとヘッダー作成とボトムのタブを作成

f:id:wheatandcat:20190626230621p:plain

そこから中身をこんな感じで作成

f:id:wheatandcat:20190626230647p:plain

ヘッダーとフッターは、他でも使用するのでコンポーネント化するf:id:wheatandcat:20190626230804p:plain

これで、他の画面でも共通で使用可能です。 ついでにアイコン類も全部読み込ませてコンポーネント化していきます

f:id:wheatandcat:20190626230957p:plain

カラーの管理は、こんな感じで作成すれば、

f:id:wheatandcat:20190626231143p:plain

こんな感じで指定できるようになるので楽に管理できます

f:id:wheatandcat:20190626232118p:plain

リニューアルしたいところを諸々作成

f:id:wheatandcat:20190626232305p:plain

さらにprototypeを設定すると画面遷移も作れます

f:id:wheatandcat:20190626232612p:plain

完成品

URLで簡単に共有できるので、こちらに貼っておきます

↓はクリックしていくと画面遷移します

使いやすくて良いツールだったので、今後も使っていこうと思います

バックアップ機能作りました

ローカルに保存だけだとアンインストールでデータ消えるとかあるので、最低限データ移行できるようにバックアップ機能を作りました。

組み込みの流れ

  • Google login 経由でfirebaseにログイン
  • Auth認証を通してGAE経由でバックアップデータをfirestoreに保存
  • バックアップデータからのリストアはフロントから直接firestoreにアクセスしてローカルのSqliteを上書き

開発

Auth0認証まで以下の記事にて

wheatandcat.hatenablog.com

それ以降は、こちらのpull requestで対応

github.com

コード量は多いいけど愚直に実装しているだけです

実装

次はデザインリニューアル作業やっていく予定

Swagger Hubを使ってみる

APIドキュメントなかったと思い、swagger書きました

add swagger close#17 by wheatandcat · Pull Request #19 · wheatandcat/Peperomia · GitHub

openapi: 3.0.0

servers:
  - url: http://localhost:8080
    description: Local server

info:
  version: 1.0.0
  title: ペペロミア API
  description: https://github.com/wheatandcat/Peperomia/tools//swagger.yaml

security:
  - bearerAuth: []   

paths:
  /CreateUser:
    post:
      tags:
        - 実装済み
      description: |
        - ユーザーを作成する
      responses:
        '201':
          description: |
            - 作成した
        '200':
          description: |
            - 既に作成済だった
  /SyncItems:
    post:
      tags:
        - 未実装
      requestBody:
        $ref: '#/components/requestBodies/SyncItemsRequest'

      responses:
        '200':
          description: |
            - 同期成功
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  requestBodies:
    SyncItemsRequest:
        description: アイテム同期のRequest
        content:
          application/json:
            schema:
              type: object
              properties:
                items:
                  type: array
                  items:
                    $ref: '#/components/schemas/Item'

                itemsDetails:
                  type: array
                  items:
                    $ref: '#/components/schemas/ItemDetail'
  schemas:
    Item:
      type: object
      description: スケジュールアイテム
      properties:
        id:
          type: integer
          format: int32
          pattern: "1"
          example: 1
          description: ID
          required:
            - id
        titie:
          type: string
          pattern: string
          example: 葛西臨海公園
          description: タイトル
        kind:
          type: string
          example: fishing
          description: アイコンの種類
        image:
          type: string
          example: ""
          description: 画像のBase64エンコード
        imagePath:
          type: string
          example: ""
          description: 画像のパス
    ItemDetail:
      type: object
      description: スケジュールアイテム詳細
      properties:
        id:
          type: integer
          format: int32
          pattern: "1"
          example: 1
          description: ID
        itemId:
          type: integer
          format: int32
          pattern: "1"
          example: 1
          description: ID
        titie:
          type: string
          pattern: string
          example: 葛西臨海公園
          description: タイトル
        kind:
          type: string
          example: fishing
          description: アイコンの種類
        memo:
          type: string
          example: ""
          description: メモ内容
        moveMinutes:
          type: integer
          pattern: "1"
          example: 30
          description: 移動時間(分)
        priority:
          type: integer
          pattern: "1"
          example: 1
          description: 表示順

swagger見れる場所欲しいなーと思い↓のサイトを見ていたけど、

openapi.tools

そういえばSwagger Hubがあったなーと思い出し使ってみた

swagger.io

使い方

ログインしてプロジェクト名を入力すると Swagger Editorっぽい感じの画面が立ち上がるので、ここで入力

f:id:wheatandcat:20190619000054p:plain

作成完了したら保存して、隣のドキュメントをクリックすれば 簡単に共有もできる

app.swaggerhub.com

全部ブラウザで済むので体験的に良かった

他にも色々機能はあるみたいだけど、一旦これだけで満足

expoでfirebase Authを実装する

backendもFirestoreに繋いでユーザー情報を保存するようにして、frontendを繋いだら、こんな感じになりました。

対象のpull request

ログイン追加 by wheatandcat · Pull Request #13 · wheatandcat/Peperomia · GitHub

expoの実装

今回はGoogle ログインを使用してFirebase Authを通すように実装しました。

blog.expo.io

基本的には↑の記事通りに実装すればOKですが以下微妙にハマるので注意

  • expo上で実行する場合はfirebase、GCPの認証情報共にバンドル名を「host.exp.exponent」にする
  • androidの署名証明書フィンガープリントは以下で発行
openssl rand -base64 32 | openssl sha1 -c

これで設定すればOKです。

まずは、app.jsonに追記

■app.json

    "ios": {
      "config": {
        "googleSignIn": {
          "reservedClientId": "*************"
        }
      }
    },
    "android": {
      "googleServicesFile": "./android/google-services.json",
    },

iOSには上記で作成したGoogleログイン認証のreservedClientIdを指定、androidは出力したgoogle-services.jsonのパスを指定

あとはFirebaseのアプリにクライアントIDを指定

■.env

GOOGLE_LOGIN_ANDROID_CLIENT_ID="**********************"
GOOGLE_LOGIN_IOS_CLIENT_ID="*************************"

あとはjsのコードは以下の感じでOK

PeperomiaNative/src/containers/Auth.tsx

  onGoogleLogin = async () => {
    const androidClientId = process.env.GOOGLE_LOGIN_ANDROID_CLIENT_ID;
    const iosClientId = process.env.GOOGLE_LOGIN_IOS_CLIENT_ID;
    const result = await Google.logInAsync({
      behavior: "web",
      iosClientId,
      androidClientId,
      scopes: ["profile", "email"]
    });

    if (result.type === "success") {
      const { idToken, accessToken } = result;
      const credential = firebase.auth.GoogleAuthProvider.credential(
        idToken,
        accessToken
      );

      await firebase.auth().signInAndRetrieveDataWithCredential(credential);

      await this.setSession(true);
    }
  };

あとはフロントエンドを整えて完成

動作

f:id:wheatandcat:20190610222710g:plain

ログインまで作ったので、あとはサーバーへのバックアップ機能を作ったら公開予定

ログイン機能を一旦リリースしたら次はスマートスピーカーとの連携辺りを試してみようと思います

GAE/Go 2nd-genでfirebase Authを実装してみる

ログイン機能を作ろうと思ったのでGAE/Goで実装してみた

GAE/Go 2nd-genとは

cloud.google.com

2nd-genの変更点は、ここでまとめられるので読むと分かるかも

github.com

実装

公式のサンプルがあるので、これを元に作成

github.com

app.yaml

■app.yaml

runtime: go111

「runtime: go111」で使えるようになります

取り敢えず、動くものをデプロイしてみる

■app.go

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run()
}

ローカルで確認

$ dev_appserver.py app.yaml

確認

$ curl http://localhost:8080/ping
{"message":"pong"}

動いているを確認したらデプロイ。。。 する前にvgoで依存管理をする

$ export GO111MODULE=on
$ go mod init
$ go mod vendor

これで、「vendor」フォルダにmoduleが入る

これでデプロイ

$ gcloud app deploy

これでAPIとして利用可能になった

firebase Authを実装

あとはfirebase Authの実装を追加

まずMiddlewareを書く

■ middleware/firebase.go

package middleware

import (
    "context"
    "net/http"
    "os"
    "strings"

    firebase "firebase.google.com/go"
    "github.com/gin-gonic/gin"
    "golang.org/x/oauth2/google"
    "google.golang.org/api/option"
)

func FirebaseAuthMiddleWare(gc *gin.Context) {
    ctx := context.Background()
    creds, err := google.CredentialsFromJSON(ctx, []byte(os.Getenv("FIREBASE_SERVICE_ACCOUNT_JSON")))
    if err != nil {
        gc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": err.Error()})
        return
    }

    opt := option.WithCredentials(creds)
    app, err := firebase.NewApp(context.Background(), nil, opt)
    if err != nil {
        gc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": err.Error()})
        return
    }

    auth, err := app.Auth(context.Background())
    if err != nil {
        gc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": err.Error()})
        return
    }

    idToken := strings.Replace(gc.GetHeader("Authorization"), "Bearer ", "", 1)
    _, err = auth.VerifyIDToken(context.Background(), idToken)
    if err != nil {
        gc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": err.Error()})
        return
    }

    gc.Next()
}

環境変数にfirebaseの設定を追加

■app.yaml

runtime: go111

env_variables:
  FIREBASE_SERVICE_ACCOUNT_JSON: |
    {
      "type": "**************",
      "project_id": "**************",
      "private_key_id": "**************",
      "private_key": "**************",
      "client_email": "**************",
      "client_id": "**************",
      "auth_uri": "**************",
      "token_uri": "**************",
      "auth_provider_x509_cert_url": "**************",
      "client_x509_cert_url": "**************"
    }
    

最後にcorsとmiddlewareの設定を追加して完了

■app.go

package main

import (
    "net/http"

    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
    "github.com/wheatandcat/Peperomia/backend/handler"
    "github.com/wheatandcat/Peperomia/backend/middleware"
)

func main() {
    r := gin.Default()

    r.Use(cors.New(cors.Config{
        AllowOrigins: []string{"https://foo.com"},
        AllowMethods: []string{
            http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodOptions,
        },
        AllowHeaders: []string{
            "Authorization",
            "Content-Type",
            "Cache-Control",
        },
        AllowCredentials: true,
        MaxAge:           86400,
    }))

    r.Use(middleware.FirebaseAuthMiddleWare)

    h := handler.NewHandler()

    r.POST("/SaveUser", h.SaveUser)
    r.Run()
}

これで取り敢えず、認証してAPIアクセスのができるはず、 次回はフロント側の実装の紹介をしようと思います。

今回の修正のpull request

Firebaseログインを追加する by wheatandcat · Pull Request #12 · wheatandcat/Peperomia · GitHub

typescript-eslintを設定してみる

Lint系の設定がなかったので追加してみました

最終的な修正

github.com

ESLintとTSLint

eslint.org

公式の発表でtslintはeslintに統合されていく方針で進んでいます。 統語には「typescript-eslint」を使います

github.com

導入は以下の通り

yarn add -D eslint @typescript-eslint/eslint-plugin  @typescript-eslint/eslint-plugin-tslint

■.eslintrc.json

  "plugins": ["@typescript-eslint"],
  "parser": "@typescript-eslint/parser",
  "env": {
    "es6": true
  },
  "rules": {
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "_"
      }
    ],
  },

これでTSLintのrulesが使えるようになります サポートしている内容については、こちらで確認

github.com

Prettier

どうせやるならPrettierも統合していく

 yarn add -D eslint-config-prettier eslint-plugin-prettier

■.eslintrc.json

  "extends": ["prettier"],
  "plugins": ["@typescript-eslint", "prettier"],
  "parser": "@typescript-eslint/parser",
  "env": {
    "es6": true
  },
  "rules": {
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "_"
      },
    "prettier/prettier": "error"
    ],
  },

これで一通りOK。

あとは各自必要なルールを設定していけばOK