用Flutter寫了個部落格園APP

xiaoyaocz發表於2022-12-13

最近在摸魚時看到了一些部落格園API文章,就想著摸魚時寫個APP練練手。

現階段實現了以下功能模組:

  • 部落格瀏覽、評論
  • 新聞瀏覽
  • 快閃記憶體瀏覽、釋出、評論
  • 博問瀏覽
  • 使用者登入

博問暫時只支援瀏覽,不支援回答提問等操作。

支援iOS、Android平臺。

截圖

淺色模式:

Light.jpg

深色模式:

Dark.jpg

API

開發前需要先到https://api.cnblogs.com/申請API KEY,申請透過才能使用部落格園的API。

輸入一下個人資訊跟應用資訊,提交後等待部落格園的稽核。透過稽核後會收到一封包含ClientId和ClientSecret的郵件。

申請APIKEY.png

部落格園API文件上的API不全,有些API需要到Github中查詢。

開發

API申請完成就可以愉快的進行開發了。
Flutter版本這次選擇了最新的3.3,基於GetX框架+Dio來開發,不得不說GetX一把梭的感覺是真的爽。

目錄結構跟GetX框架差不多,按我自己的習慣進行了一些改動:

  • app 一些通用的類及樣式
  • services 提供資料儲存等服務
  • requests 請求的封裝
  • generated 生成的國際化檔案,使用 get generate locales生成
  • modules 模組,每個會有兩個檔案,view及controller
  • widgets 自定義的小元件
  • routes 路由定義
  • models 實體類

個人練手專案就加上了一些以前沒用過包跟特性體驗一下(如lottie、getx國際化)。

這個專案比較簡單,技術上沒什麼好說的,主要說下博文的展示:

博文展示

博文由於是HTML富文字且有一些複雜樣式,把文章內容轉為Flutter Widget顯示的體驗挺差,所以還是使用WebView來展示。

在這裡我選擇了flutter_inappwebview包,這個包會比官方的webview_flutter功能更加豐富,更新也更頻繁。

HTML載入流程如下:

  1. 在assets中新增HTML,預留內容、js、css槽位
  2. 在assets中新增兩個css樣式,分別對應淺色模式及深色模式;css內容可以從任意文章的Web頁面抓取
  3. 在assets中新增JS,編寫WebView與APP的互動
  4. 在assets中新增highlight.js,實現程式碼的高亮
  5. 將js、css填充至HTML,css需要根據APP主題選擇
  6. 透過API讀取博文內容,並填充到HTML中
  7. 透過webViewController.loadData(data: html)在APP中顯示博文

具體實現可以看原始碼。實現效果:

博文.jpg

由於專案依賴WebView(文章、登入),所以只支援iOS、Android平臺。要支援其他平臺也不難,只需要把文章內容轉為Flutter Widget顯示再更改下登入邏輯即可。

自動打包

使用Github Actions可以很簡單的自動完成打包釋出的步驟。

關於Github Actions的介紹,可以看下官方文件

在專案建立根目錄建立一個.github/workflows,然後建立一個xxxx.yml,寫入以下程式碼。

name: app-build-action
#推送Tag時觸發
on:
  push:
    tags:
      - "*"
jobs:
  build-ios-android:
    # 使用macOS映象,如果不需要打包iOS,可以替換成Ubuntu
    runs-on: macos-latest
    permissions:
      contents: write
    steps:
      #簽出程式碼 
      - uses: actions/checkout@v2
        with:
          ref: master
       #寫出ENV檔案
      - name: Create .env
        run: |
          echo "CLIENT_ID=${{ secrets.CLIENT_ID }}" > .env
          echo "CLIENT_SECRET=${{ secrets.CLIENT_SECRET }}" >> .env
      #APK簽名設定
      - name: Download Android keystore
        id: android_keystore
        uses: timheuer/base64-to-file@v1.2
        with:
          fileName: keystore.jks
          encodedString: ${{ secrets.KEYSTORE_BASE64 }}
      - name: Create key.properties
        run: |
          echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
          echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/key.properties
          echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
          echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties
      #設定JAVA環境
      - uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: "12.x"
          cache: 'gradle'
      #設定Flutter
      - name: Flutter action
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.3.9'
          cache: true 
      - name: Restore packages
        run: flutter pub get
      #打包APK
      - name: Build APK
        run: flutter build apk --release
      #上傳APK至Artifacts
      - name: Upload APK to Artifacts
        uses: actions/upload-artifact@v3
        with:
          name: app-release.apk
          path: |
            build/app/outputs/flutter-apk/app-release.apk
      #打包iOS
      - name: Build IPA
        run: flutter build ios --release --no-codesign
      #建立未簽名ipa
      - name: Create IPA
        run: |
          mkdir build/ios/iphoneos/Payload
          cp -R build/ios/iphoneos/Runner.app build/ios/iphoneos/Payload/Runner.app
          zip -q -r build/ios/iphoneos/ios_no_sign.ipa build/ios/iphoneos/Payload
      #上傳IPA至Artifacts
      - name: Upload IPA to Artifacts
        uses: actions/upload-artifact@v3
        with:
          name: ios_no_sign.ipa
          path: |
            build/ios/iphoneos/ios_no_sign.ipa
      #從document/new_version.json讀取版本資訊
      - name: Read version
        id: version
        uses: juliangruber/read-file-action@v1
        with:
          path: document/new_version.json
      - name: Echo version
        run: echo "${{ fromJson(steps.version.outputs.content).version }}"
      - name: Echo version content
        run: echo "${{ fromJson(steps.version.outputs.content).version_desc }}"
      #釋出至Github Release
      - name: Upload Release
        uses: ncipollo/release-action@v1
        with:
          allowUpdates: true
          artifactErrorsFailBuild: true
          artifacts: "build/app/outputs/flutter-apk/app-release.apk,build/ios/iphoneos/ios_no_sign.ipa"
          name: "${{ fromJson(steps.version.outputs.content).version }}"
          body: "${{ fromJson(steps.version.outputs.content).version_desc }}"
          token: ${{ secrets.TOKEN }}
      #完成
      - run: echo "? This job's status is ${{ job.status }}."

執行指令碼前,需要先到Github專案設定-Secrets-Actions中新增以下欄位

CLIENT_ID 部落格園API ClientId
CLIENT_SECRET 部落格園API ClientSecret
KEYSTORE_BASE64 APK簽名檔案,轉為BASE64
KEY_ALIAS APK簽名keyAlias
KEY_PASSWORD APK簽名keyPassword
STORE_PASSWORD APK簽名storePassword
TOKEN Github Token

Github Token點這裡新增,注意需要許可權的設定。

secrets.png

設定完成後,當想要打包一個新版本時,只需要提交程式碼並打上Tag推送至Github,即可自動打包。

總結

這個專案沒啥難度且部落格園API文件還是比較全面,基本對著API做個UI就行了。

天冷變懶了,有些功能還沒有寫完,過段時間再慢慢完善了。

專案開源地址:https://github.com/xiaoyaocz/flutter_cnblogs,有興趣可以到Release中下載安裝包體驗一下。

相關文章